economics.tasks
One record per task, folded from the ledger of every checkout `git worktree list` reports and the archives a ledger rotation left: its issue and intent, checkout and branch, first start, last finish or refusal, wall seconds, refusal count, and the episodes that carried it with their spans. Interventions, CI reruns, spend and acceptance are present on every record as not_observed, each with the reason and the issue that will supply it. Lists every ledger read with its line and skipped-line counts and every checkout not read; an optional since/until window filters by each task's start.
One record per task, folded from the ledger of every checkout `git worktree list` reports and the archives a ledger rotation left: its issue and intent, checkout and branch, first start, last finish or refusal, wall seconds, refusal count, and the episodes that carried it with their spans. Interventions, CI reruns, spend and acceptance are present on every record as not_observed, each with the reason and the issue that will supply it. Lists every ledger read with its line and skipped-line counts and every checkout not read; an optional since/until window filters by each task's start.
- query
- behaviorally_verified
- module economics
- #economics
- #evidence
- #delivery
Zpřístupnění
Kde je tato jedna definice dosažitelná. Absence je explicitní: povrch, který není uveden, ji nenese.
| povrch | jako |
|---|---|
| MCP | tool majordomus_economics_tasks |
| HTTP | GET /api/v1/economics/tasks operationId economics.tasks |
| příkazová řádka | majordomus economics tasks |
Schémata
Kanonický vstup a výstup z typů Rustu; schéma nástroje MCP a parametry a odpovědi OpenAPI se odvozují z nich.
input · TaskEconomicsQuery
task.started time, so a task
with no start line in the ledgers read is answered only when no window is given.
use majordomus_cli::economics::tasks::TaskEconomicsQuery;
let q: TaskEconomicsQuery = serde_json::from_str(r#"{"since":"2026-10-21T00:00:00Z"}"#).unwrap();
assert_eq!(q.since.as_deref(), Some("2026-10-21T00:00:00Z"));
assert!(q.until.is_none());
assert_eq!(serde_json::from_str::<TaskEconomicsQuery>("{}").unwrap(), TaskEconomicsQuery::default());
// a misspelt bound is refused, never read as no bound
assert!(serde_json::from_str::<TaskEconomicsQuery>(r#"{"after":"x"}"#).is_err());| vlastnost | typ | povinné | popis |
|---|---|---|---|
| od | string | null | ne | Answer only tasks started at or after this time (YYYY-MM-DDTHH:MM:SSZ). |
| do | string | null | ne | Answer only tasks started before this time (YYYY-MM-DDTHH:MM:SSZ). |
JSON Schema
{
"additionalProperties": false,
"description": "Which tasks to answer. A window filters by each task's `task.started` time, so a task\nwith no start line in the ledgers read is answered only when no window is given.\n\n```\nuse majordomus_cli::economics::tasks::TaskEconomicsQuery;\nlet q: TaskEconomicsQuery = serde_json::from_str(r#\"{\"since\":\"2026-10-21T00:00:00Z\"}\"#).unwrap();\nassert_eq!(q.since.as_deref(), Some(\"2026-10-21T00:00:00Z\"));\nassert!(q.until.is_none());\nassert_eq!(serde_json::from_str::<TaskEconomicsQuery>(\"{}\").unwrap(), TaskEconomicsQuery::default());\n// a misspelt bound is refused, never read as no bound\nassert!(serde_json::from_str::<TaskEconomicsQuery>(r#\"{\"after\":\"x\"}\"#).is_err());\n```",
"properties": {
"since": {
"description": "Answer only tasks started at or after this time (`YYYY-MM-DDTHH:MM:SSZ`).",
"type": [
"string",
"null"
]
},
"until": {
"description": "Answer only tasks started before this time (`YYYY-MM-DDTHH:MM:SSZ`).",
"type": [
"string",
"null"
]
}
},
"title": "TaskEconomicsQuery",
"type": "object"
}output · TaskEconomicsList
use majordomus_cli::economics::tasks::{fold, TaskEconomicsList, TaskEconomicsQuery, TaskEconomicsSource};
let text = r#"{"ts":"2026-10-21T10:00:00Z","event":"task.started","head":"h","branch":"b","by":"me","session":"s-1","task_id":"t-1"}"#;
let source = TaskEconomicsSource::parse("repo", "ledger.jsonl", text);
let list: TaskEconomicsList = fold(vec![source], &TaskEconomicsQuery::default());
assert_eq!((list.count, list.tasks.len()), (1, 1));
assert_eq!(list.ledgers[0].path, "ledger.jsonl");
assert!(list.unread.is_empty(), "fold reads no checkout, so it names none unread");| vlastnost | typ | povinné | popis |
|---|---|---|---|
| počet | integer | ano | How many tasks are answered: the number of distinct task ids within the window. |
| ledgers | array | ano | Every ledger file read, with its line and skipped-line counts. |
| úlohy | array | ano | One record per task, in the order the tasks started. |
| nepřečteno | array | ano | Every checkout whose ledger could not be read, and why. |
| okno | reference | ano | The window the tasks were filtered by, as given. |
JSON Schema
{
"$defs": {
"TaskEconomicsEpisode": {
"description": "One episode that carried a task, with the span of the lines it wrote about it.\n\n```\nuse majordomus_cli::economics::tasks::{fold, TaskEconomicsEpisode, TaskEconomicsSource};\nlet line = |ts: &str, event: &str, session: &str| {\n format!(r#\"{{\"ts\":\"{ts}\",\"event\":\"{event}\",\"head\":\"h\",\"branch\":\"b\",\"by\":\"me\",\"session\":\"{session}\",\"task_id\":\"t-1\"}}\"#)\n};\nlet text = [\n line(\"2026-10-21T10:00:00Z\", \"task.started\", \"s-1\"),\n line(\"2026-10-21T11:00:00Z\", \"task.finished\", \"s-2\"),\n]\n.join(\"\\n\");\nlet list = fold(vec![TaskEconomicsSource::parse(\"repo\", \"ledger.jsonl\", &text)], &Default::default());\nlet episodes: &[TaskEconomicsEpisode] = &list.tasks[0].episodes;\nassert_eq!(episodes.len(), 2, \"two session stamps are two episodes\");\nassert_eq!((episodes[1].session.as_str(), episodes[1].lines), (\"s-2\", 1));\nassert_eq!(episodes[1].first_at, \"2026-10-21T11:00:00Z\");\n```",
"properties": {
"first_at": {
"description": "The time of the episode's first line about the task.",
"type": "string"
},
"last_at": {
"description": "The time of the episode's last line about the task.",
"type": "string"
},
"lines": {
"description": "How many of the episode's lines are about the task.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"provider": {
"description": "The provider that ran the episode, when the ledger names it. Left out of the answer\nwhen it does not, so it reads back as empty: without `default` the text form, which\nreads the capability's JSON, refused every episode whose provider is unknown.",
"type": "string"
},
"session": {
"description": "The episode's session id.",
"type": "string"
}
},
"required": [
"session",
"first_at",
"last_at",
"lines"
],
"type": "object"
},
"TaskEconomicsField": {
"description": "A field of the record that nothing observes yet, carrying the reason and the issue that\nwill supply it in place of a value.\n\n```\nuse majordomus_cli::economics::tasks::{fold, TaskEconomicsField, TaskEconomicsSource};\nlet text = r#\"{\"ts\":\"2026-10-21T10:00:00Z\",\"event\":\"task.started\",\"head\":\"h\",\"branch\":\"b\",\"by\":\"me\",\"task_id\":\"t-1\"}\"#;\nlet list = fold(vec![TaskEconomicsSource::parse(\"repo\", \"ledger.jsonl\", text)], &Default::default());\nlet field: &TaskEconomicsField = &list.tasks[0].ci_reruns;\nlet v = serde_json::to_value(field).unwrap();\nassert_eq!(v[\"status\"], \"not_observed\");\nassert!(field.reason.contains(\"I3378\"), \"{}\", field.reason);\n```",
"properties": {
"reason": {
"description": "Why there is no value, naming what will supply it.",
"type": "string"
},
"status": {
"$ref": "#/$defs/TaskEconomicsFieldStatus",
"description": "Always `not_observed` until the field's supplier lands."
}
},
"required": [
"status",
"reason"
],
"type": "object"
},
"TaskEconomicsFieldStatus": {
"description": "Whether a field holds an observation. Its one variant says there is none, which is not a\nzero.\n\n```\nuse majordomus_cli::economics::tasks::TaskEconomicsFieldStatus;\nlet v = serde_json::to_value(TaskEconomicsFieldStatus::NotObserved).unwrap();\nassert_eq!(v, \"not_observed\");\n```",
"oneOf": [
{
"const": "not_observed",
"description": "Nothing observes the field yet: there is no value, which is not a zero.",
"type": "string"
}
]
},
"TaskEconomicsLedger": {
"description": "One ledger file that was read, with how many of its lines were entries and how many\ncould not be read as one.\n\n```\nuse majordomus_cli::economics::tasks::{TaskEconomicsLedger, TaskEconomicsSource};\nlet s = TaskEconomicsSource::parse(\"repo\", \".ai/local/state/ledger.jsonl\", \"{}\\nnot json\\n\");\nlet ledger: &TaskEconomicsLedger = &s.ledgers[0];\nassert_eq!(ledger.checkout, \"repo\");\nassert_eq!(ledger.path, \".ai/local/state/ledger.jsonl\");\nassert_eq!((ledger.lines, ledger.skipped), (0, 2), \"neither line is a ledger entry\");\n```",
"properties": {
"checkout": {
"description": "The checkout the ledger belongs to.",
"type": "string"
},
"lines": {
"description": "How many lines were read as ledger entries.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"path": {
"description": "The file, relative to the checkout.",
"type": "string"
},
"skipped": {
"description": "How many non-empty lines could not be read as ledger entries.",
"format": "uint",
"minimum": 0,
"type": "integer"
}
},
"required": [
"checkout",
"path",
"lines",
"skipped"
],
"type": "object"
},
"TaskEconomicsQuery": {
"additionalProperties": false,
"description": "Which tasks to answer. A window filters by each task's `task.started` time, so a task\nwith no start line in the ledgers read is answered only when no window is given.\n\n```\nuse majordomus_cli::economics::tasks::TaskEconomicsQuery;\nlet q: TaskEconomicsQuery = serde_json::from_str(r#\"{\"since\":\"2026-10-21T00:00:00Z\"}\"#).unwrap();\nassert_eq!(q.since.as_deref(), Some(\"2026-10-21T00:00:00Z\"));\nassert!(q.until.is_none());\nassert_eq!(serde_json::from_str::<TaskEconomicsQuery>(\"{}\").unwrap(), TaskEconomicsQuery::default());\n// a misspelt bound is refused, never read as no bound\nassert!(serde_json::from_str::<TaskEconomicsQuery>(r#\"{\"after\":\"x\"}\"#).is_err());\n```",
"properties": {
"since": {
"description": "Answer only tasks started at or after this time (`YYYY-MM-DDTHH:MM:SSZ`).",
"type": [
"string",
"null"
]
},
"until": {
"description": "Answer only tasks started before this time (`YYYY-MM-DDTHH:MM:SSZ`).",
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"TaskEconomicsRecord": {
"description": "One task's delivery economics: who carried it, when, how often `finish` refused it, and\nthe fields nothing observes yet, each saying why it has no value.\n\n```\nuse majordomus_cli::economics::tasks::{\n fold, TaskEconomicsRecord, TaskEconomicsSource, TaskEconomicsState,\n};\nlet text = r#\"{\"ts\":\"2026-10-21T10:00:00Z\",\"event\":\"task.started\",\"head\":\"h\",\"branch\":\"b\",\n \"by\":\"me\",\"session\":\"s-1\",\"task_id\":\"t-1\",\"issue\":\"I3376\"}\"#;\nlet source = TaskEconomicsSource::parse(\"repo\", \"ledger.jsonl\", &text.replace('\\n', \"\"));\nlet list = fold(vec![source], &Default::default());\nlet r: &TaskEconomicsRecord = &list.tasks[0];\nassert_eq!((r.task_id.as_str(), r.branch.as_str()), (\"t-1\", \"b\"));\nassert_eq!(r.state, TaskEconomicsState::Open);\nassert_eq!(r.outcome, \"active\");\nassert_eq!(r.wall_seconds, None, \"an open task with no refusal has no end\");\nassert_eq!(r.issue.as_deref(), Some(\"I3376\"));\nassert!(r.spend.reason.contains(\"I3379\"));\n```",
"properties": {
"accepted": {
"$ref": "#/$defs/TaskEconomicsField",
"description": "Whether the task's result was accepted."
},
"branch": {
"description": "The branch the task started on.",
"type": "string"
},
"checkout": {
"description": "The checkout whose ledger carries the task.",
"type": "string"
},
"ci_reruns": {
"$ref": "#/$defs/TaskEconomicsField",
"description": "CI runs of the task's work that were rerun."
},
"ended_at": {
"description": "The time of the task's last `task.finished` line, or of its last `task.refused` line\nwhile it has none.",
"type": [
"string",
"null"
]
},
"episodes": {
"description": "The episodes that carried the task, each with the span of its lines about it.",
"items": {
"$ref": "#/$defs/TaskEconomicsEpisode"
},
"type": "array"
},
"intent": {
"description": "The intent the task was started under, when it named one.",
"type": [
"string",
"null"
]
},
"interventions": {
"$ref": "#/$defs/TaskEconomicsField",
"description": "Human interventions during the task."
},
"issue": {
"description": "The issue the task was started under, when it named one.",
"type": [
"string",
"null"
]
},
"outcome": {
"description": "The outcome as recorded: the finish's outcome, or `active` / `handed_over` while open.",
"type": "string"
},
"refusals": {
"description": "How many times `finish` refused the task.",
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"spend": {
"$ref": "#/$defs/TaskEconomicsField",
"description": "What the task cost."
},
"started_at": {
"description": "The time of the task's first `task.started` line.",
"type": [
"string",
"null"
]
},
"state": {
"$ref": "#/$defs/TaskEconomicsState",
"description": "`finished` once a `task.finished` line is read, `open` otherwise."
},
"task_id": {
"description": "The task's id, as `start` wrote it.",
"type": "string"
},
"wall_seconds": {
"description": "Seconds from `started_at` to `ended_at`; absent while either is.",
"format": "int64",
"type": [
"integer",
"null"
]
}
},
"required": [
"task_id",
"checkout",
"branch",
"state",
"outcome",
"refusals",
"episodes",
"interventions",
"ci_reruns",
"spend",
"accepted"
],
"type": "object"
},
"TaskEconomicsState": {
"description": "Whether a task has finished, as the command line, MCP and HTTP spell it.\n\n```\nuse majordomus_cli::economics::tasks::TaskEconomicsState;\nassert_eq!(serde_json::to_value(TaskEconomicsState::Open).unwrap(), \"open\");\nassert_eq!(serde_json::to_value(TaskEconomicsState::Finished).unwrap(), \"finished\");\n```",
"oneOf": [
{
"const": "open",
"description": "No `task.finished` line was read for the task.",
"type": "string"
},
{
"const": "finished",
"description": "A `task.finished` line was read for the task.",
"type": "string"
}
]
},
"TaskEconomicsUnread": {
"description": "A checkout whose ledger was not read, and the reason, so a missing ledger is never read\nas a checkout with no tasks.\n\n```\nuse majordomus_cli::economics::tasks::{tasks, TaskEconomicsQuery, TaskEconomicsUnread};\nlet dir = tempfile::tempdir().unwrap();\nlet list = tasks(dir.path(), &TaskEconomicsQuery::default());\nlet unread: &TaskEconomicsUnread = &list.unread[0];\nassert_eq!(unread.checkout, dir.path().display().to_string());\nassert_eq!(unread.reason, \"no ledger under .ai/local/state\");\n```",
"properties": {
"checkout": {
"description": "The checkout.",
"type": "string"
},
"reason": {
"description": "Why its ledger was not read.",
"type": "string"
}
},
"required": [
"checkout",
"reason"
],
"type": "object"
}
},
"description": "Every task the ledgers read carry, and what was read to answer it.\n\n```\nuse majordomus_cli::economics::tasks::{fold, TaskEconomicsList, TaskEconomicsQuery, TaskEconomicsSource};\nlet text = r#\"{\"ts\":\"2026-10-21T10:00:00Z\",\"event\":\"task.started\",\"head\":\"h\",\"branch\":\"b\",\"by\":\"me\",\"session\":\"s-1\",\"task_id\":\"t-1\"}\"#;\nlet source = TaskEconomicsSource::parse(\"repo\", \"ledger.jsonl\", text);\nlet list: TaskEconomicsList = fold(vec![source], &TaskEconomicsQuery::default());\nassert_eq!((list.count, list.tasks.len()), (1, 1));\nassert_eq!(list.ledgers[0].path, \"ledger.jsonl\");\nassert!(list.unread.is_empty(), \"fold reads no checkout, so it names none unread\");\n```",
"properties": {
"count": {
"description": "How many tasks are answered: the number of distinct task ids within the window.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"ledgers": {
"description": "Every ledger file read, with its line and skipped-line counts.",
"items": {
"$ref": "#/$defs/TaskEconomicsLedger"
},
"type": "array"
},
"tasks": {
"description": "One record per task, in the order the tasks started.",
"items": {
"$ref": "#/$defs/TaskEconomicsRecord"
},
"type": "array"
},
"unread": {
"description": "Every checkout whose ledger could not be read, and why.",
"items": {
"$ref": "#/$defs/TaskEconomicsUnread"
},
"type": "array"
},
"window": {
"$ref": "#/$defs/TaskEconomicsQuery",
"description": "The window the tasks were filtered by, as given."
}
},
"required": [
"count",
"window",
"tasks",
"ledgers",
"unread"
],
"title": "TaskEconomicsList",
"type": "object"
}Politiky
- benchmark
- povinné — cíl na každém transportu, který expozice deklaruje; případy jsou ty ze vstupního typu
- cache
- vypnuto — každé volání spustí handler
Cíle benchmarku
Odvozeno z registru pro tento repozitář: jeden požadavek na transport a případy, které poskytuje typ vstupu. Celá matice je na stránce měření výkonu.