Skip to main content
Glama

Shift work: clock out

shiftwork_clock_out
Destructive

Record a shift-work unit as finished by setting its status and updating the checkpoint's handoff, history, and accounting in one validated atomic write.

Instructions

Shift-work clock-out: record that a unit finished — set its status, advance plan.cursor, merge handoff_patch into handoff, push history_entry onto the 5-entry history ring, and append one accounting line to .log.jsonl. Call it once per unit after its agent returns: shiftwork_clock_in briefs, this tool closes, and shiftwork_status reads what both wrote. The whole mutated document is validated against the checkpoint schema BEFORE one atomic write, so a failure writes nothing and answers {"result": "error", "reason": ...}; nothing is ever deleted from the checkpoint. unit_id must be plan.cursor — which needs no brief and clocks out with briefed=false — or a unit clock_in briefed since its own last clock-out; any other id is refused with unit X is not the cursor unit Y. handoff_patch and history_entry are both required arguments: pass {} to leave handoff unchanged, and give history_entry at least unit and outcome. plan.cursor then moves to the head of the ready batch recomputed on the new document — plan.units order over the remaining non-terminal units when the graph cannot batch, unit_id when none remain. The accounting line carries unit, role, status, ts and briefed (written by the runtime, never taken from you: true only when a brief was issued since this unit's LAST clock-out) plus your accounting fields when given. follow_ups appends the work it discovered as todo units. agreements adopts or retires a working agreement every later brief carries. Each clock-out also appends {status, outcome, notes?, produced?} to the unit's attempts (newest 3 kept). With job.budget declared, a line reporting 0 tokens is refused unless it carries estimate: true. When job.roles names this unit's role, accounting.model must be on that role's list, spelled exactly, or nothing is written — see the model property.

Input Schema

NameRequiredDescriptionDefault
statusYesThe unit's new status; the same five values the checkpoint schema allows on plan.units[].status.
unit_idYesThe unit being clocked out. Must be plan.cursor, or a unit briefed since its own last clock-out: clock_in briefs the cursor unit by default and, when its unit_id names one, any unit shiftwork_plan reports in `ready` — and that brief is what lets clock_out accept a unit the cursor is not on.
agent_idNoThe `agentId` the Agent tool's result returned for this unit's subagent (#266), copied verbatim: 1 to 64 letters, digits, - or _, or nothing is written. It goes on the accounting line, never into the checkpoint, and is how the skill miner attributes the skill uses the hook logged inside that subagent to this unit. Omit it for a unit run inline.
accountingNoPer-unit cost line, or null (the default) to log a line with no cost figures at all. A non-null line must carry `tokens` and `duration_ms`; `model` is required when job.roles names this unit's role. The object shape is described below.
agreementsNoA lesson this unit wants every later brief to carry (#261): retire by exact text, then adopt. Refused, nothing written, for text not on the list or a result over 20; each applied change is recorded in retro[].
checkpointYesPath to the shift-work checkpoint JSON file (by convention .shiftwork/checkpoint.json in the repo the job works on), absolute or relative to the server's working directory. The same file shiftwork_plan, shiftwork_clock_in and shiftwork_clock_out take; it must validate against assets/schemas/shiftwork-checkpoint.json.
conclusionNoAn investigation's answer (#264), written to job.conclusion. Only a unit with `concludes: true` may send it; `resolved` is refused unless `supported` has a done run, a done replay of that run in another role, and a done review of either. A run unit (`tests` or `replay_of`) clocked out done must report a `run-output` in history_entry.produced.
follow_upsNoWork this unit discovered (#260): each item becomes a todo unit appended to plan.units in the given order, on the same write. Refused whole, nothing written, on a duplicate id, a brief that is not a file, an unknown dependency or a cycle (the planner's sentence), or a role job.roles does not declare.
handoff_patchYesShallow-merged into `handoff`: only these four keys exist there, and a key outside them is refused by the schema check. Required; `{}` leaves `handoff` unchanged.
history_entryYesPushed onto the 5-entry `history` ring; `unit` and `outcome` are required, `notes` optional, other keys pass through.

Output Schema

NameRequiredDescriptionDefault
logNoPath of the <checkpoint>.log.jsonl the accounting line was appended to.
unitNoThe unit clocked out.
cursorNoplan.cursor after the move.
reasonNoWith result `error`: one sentence saying why the call was refused.
resultYes`ok` when the checkpoint was rewritten; `error` with `reason` when the call was refused and nothing was written.
statusNoThe status it now holds.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed11 schema fields changedv0.38.0
    • changedInput schema / properties / accounting / anyOf
      Previous value: -[
      -  {
      -    "additionalProperties": true,
      -    "description": "Per-unit cost. Written verbatim, beside ts/unit/role/status, as one line of <checkpoint>.log.jsonl - the ledger the N-sessions experiment reads. The keys named here have fixed meanings so a reader holding only the ledger knows what each number counts, in which unit, and which model produced it; any other key passes through unchanged, except the five the runtime writes itself — unit, role, status, ts, event — which are refused (result=error) before anything is written; a self-reported briefed is overwritten, not refused. Lines written before this schema existed may spell these differently; the schema governs new writes only.",
      -    "properties": {
      -      "cache_read_tokens": {
      -        "description": "Tokens the unit's requests served from prompt cache, kept OUT of `tokens` because they dominate a real session's traffic and would swamp the work signal. Optional: omit it when the harness reports no such figure - a written 0 means the unit read nothing from cache.",
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "duration_ms": {
      -        "description": "Wall-clock from spawning the subagent to its final message, in whole milliseconds. In a non-null line it is required, and it is the only duration key with a defined meaning: older lines carry `duration`, `duration_min` or `duration_s`, which this schema neither reads nor renames.",
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "model": {
      -        "description": "The exact identifier of the model the subagent actually ran on (e.g. `claude-sonnet-5`) and nothing else - a caveat about how it was chosen belongs in `note`. Not required here: when the checkpoint's job.roles names this unit's role, clock_out already requires it and refuses a value off that role's list, spelled exactly; a checkpoint with no job.roles leaves it optional. This schema does not change that gate.",
      -        "type": "string"
      -      },
      -      "note": {
      -        "description": "Free text for what the fields above cannot say: that `tokens` is an estimate, that this is a retry or a second scope under the same unit id, what a harness counter excludes. Anything that is not a model identifier goes here, not in `model`.",
      -        "type": "string"
      -      },
      -      "tokens": {
      -        "description": "Tokens the unit consumed as the harness's subagent counter reports them: every class it reports (input, output, cache creation) summed, EXCLUDING cache reads. In a non-null accounting line it is required — a line without it is refused before anything is written; only `accounting: null`, the default, is exempt, and that logs no cost at all. A rounded self-estimate is allowed only if `note` says it is one; a unit whose cost is unknown cannot be compared with any other.",
      -        "minimum": 0,
      -        "type": "integer"
      -      },
      -      "tool_uses": {
      -        "description": "Tool calls the subagent made, as the harness counts them. Optional; it is the denominator that makes `tokens` comparable across units of different size (per-unit cost is linear in tool calls, not in unit length).",
      -        "minimum": 0,
      -        "type": "integer"
      -      }
      -    },
      -    "required": [
      -      "tokens",
      -      "duration_ms"
      -    ],
      -    "type": "object"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": true,
      +    "description": "Per-unit cost. Written verbatim, beside ts/unit/role/status, as one line of <checkpoint>.log.jsonl - the ledger the N-sessions experiment reads. The keys named here have fixed meanings so a reader holding only the ledger knows what each number counts, in which unit, and which model produced it; any other key passes through unchanged, except the six the runtime writes itself — unit, role, status, ts, event, agent_id — which are refused (result=error) before anything is written; a self-reported briefed is overwritten, not refused. Lines written before this schema existed may spell these differently; the schema governs new writes only.",
      +    "properties": {
      +      "cache_read_tokens": {
      +        "description": "Tokens the unit's requests served from prompt cache, kept OUT of `tokens` because they dominate a real session's traffic and would swamp the work signal. Optional: omit it when the harness reports no such figure - a written 0 means the unit read nothing from cache.",
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "duration_ms": {
      +        "description": "Wall-clock from spawning the subagent to its final message, in whole milliseconds. In a non-null line it is required, and it is the only duration key with a defined meaning: older lines carry `duration`, `duration_min` or `duration_s`, which this schema neither reads nor renames.",
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "estimate": {
      +        "description": "true when `tokens` is a guess rather than the host's figure. With job.budget declared, a line reporting 0 tokens is refused unless it carries estimate: true.",
      +        "type": "boolean"
      +      },
      +      "model": {
      +        "description": "The exact identifier of the model the subagent actually ran on (e.g. `claude-sonnet-5`) and nothing else - a caveat about how it was chosen belongs in `note`. Not required here: when the checkpoint's job.roles names this unit's role, clock_out already requires it and refuses a value off that role's list, spelled exactly; a checkpoint with no job.roles leaves it optional. This schema does not change that gate.",
      +        "type": "string"
      +      },
      +      "note": {
      +        "description": "Free text for what the fields above cannot say: that `tokens` is an estimate, that this is a retry or a second scope under the same unit id, what a harness counter excludes. Anything that is not a model identifier goes here, not in `model`.",
      +        "type": "string"
      +      },
      +      "tokens": {
      +        "description": "Tokens the unit consumed as the harness's subagent counter reports them: every class it reports (input, output, cache creation) summed, EXCLUDING cache reads. In a non-null accounting line it is required — a line without it is refused before anything is written; only `accounting: null`, the default, is exempt, and that logs no cost at all. A rounded self-estimate is allowed only if `note` says it is one; a unit whose cost is unknown cannot be compared with any other.",
      +        "minimum": 0,
      +        "type": "integer"
      +      },
      +      "tool_uses": {
      +        "description": "Tool calls the subagent made, as the harness counts them. Optional; it is the denominator that makes `tokens` comparable across units of different size (per-unit cost is linear in tool calls, not in unit length).",
      +        "minimum": 0,
      +        "type": "integer"
      +      }
      +    },
      +    "required": [
      +      "tokens",
      +      "duration_ms"
      +    ],
      +    "type": "object"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedInput schema / properties / agent_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "The `agentId` the Agent tool's result returned for this unit's subagent (#266), copied verbatim: 1 to 64 letters, digits, - or _, or nothing is written. It goes on the accounting line, never into the checkpoint, and is how the skill miner attributes the skill uses the hook logged inside that subagent to this unit. Omit it for a unit run inline.",
      +  "title": "Agent Id"
      +}
    • addedInput schema / properties / agreements
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": false,
      +      "properties": {
      +        "adopt": {
      +          "description": "Agreements to add to job.agreements; text already listed is left as it is.",
      +          "items": {
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "type": "array"
      +        },
      +        "retire": {
      +          "description": "Agreements to remove, by exact text; one not on the list is refused.",
      +          "items": {
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "type": "array"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "A lesson this unit wants every later brief to carry (#261): retire by exact text, then adopt. Refused, nothing written, for text not on the list or a result over 20; each applied change is recorded in retro[].",
      +  "title": "Agreements"
      +}
    • addedInput schema / properties / conclusion
      Added value: +{
      +  "anyOf": [
      +    {
      +      "additionalProperties": false,
      +      "properties": {
      +        "evidence": {
      +          "description": "Refs to what was observed: run outputs, commits, files.",
      +          "items": {
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "type": "array"
      +        },
      +        "outcome": {
      +          "description": "`inconclusive` and `stopped` are never refused; `resolved` needs the evidence below.",
      +          "enum": [
      +            "resolved",
      +            "inconclusive",
      +            "stopped"
      +          ],
      +          "type": "string"
      +        },
      +        "supported": {
      +          "description": "The hypothesis id the evidence supports; required for `resolved`.",
      +          "minLength": 1,
      +          "type": "string"
      +        }
      +      },
      +      "required": [
      +        "outcome",
      +        "evidence"
      +      ],
      +      "type": "object"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "An investigation's answer (#264), written to job.conclusion. Only a unit with `concludes: true` may send it; `resolved` is refused unless `supported` has a done run, a done replay of that run in another role, and a done review of either. A run unit (`tests` or `replay_of`) clocked out done must report a `run-output` in history_entry.produced.",
      +  "title": "Conclusion"
      +}
    • addedInput schema / properties / follow_ups
      Added value: +{
      +  "anyOf": [
      +    {
      +      "items": {
      +        "properties": {
      +          "brief_path": {
      +            "description": "Its brief, a file that must already exist, relative to the repo (the nearest ancestor of the checkpoint holding .git).",
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "depends_on": {
      +            "description": "Unit ids it waits for; absent means the unit being clocked out.",
      +            "items": {
      +              "minLength": 1,
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "id": {
      +            "description": "The new unit's id; must not be a unit already, nor repeat in this list.",
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "role": {
      +            "description": "Who runs it; when job.roles is declared it must be one of its keys.",
      +            "enum": [
      +              "planner",
      +              "implementer",
      +              "reviewer"
      +            ],
      +            "type": "string"
      +          },
      +          "title": {
      +            "description": "One line naming the work.",
      +            "minLength": 1,
      +            "type": "string"
      +          },
      +          "touches": {
      +            "description": "Repo-relative paths it will write (#257).",
      +            "items": {
      +              "type": "string"
      +            },
      +            "type": "array"
      +          },
      +          "verify": {
      +            "description": "Command proving it done; absent means the clocked-out unit's verify.",
      +            "minLength": 1,
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "id",
      +          "title",
      +          "role",
      +          "brief_path"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "Work this unit discovered (#260): each item becomes a todo unit appended to plan.units in the given order, on the same write. Refused whole, nothing written, on a duplicate id, a brief that is not a file, an unknown dependency or a cycle (the planner's sentence), or a role job.roles does not declare.",
      +  "title": "Follow Ups"
      +}
    • changedInput schema / properties / handoff_patch / properties / open_questions / description
      Previous value: -"The autonomy switch: empty = driver keeps cycling, non-empty = STOP and ask."New value: +"The autonomy switch: empty = driver keeps cycling, non-empty = STOP and ask. A bare string is a decision; a `relay` question carries a `probe` that clock_in runs instead of escalating (#225)."
    • addedInput schema / properties / handoff_patch / properties / open_questions / items / anyOf
      Added value: +[
      +  {
      +    "description": "A decision question: the user's to answer.",
      +    "minLength": 1,
      +    "type": "string"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "properties": {
      +      "kind": {
      +        "description": "`decision` needs the user; `relay` is answered by its probe.",
      +        "enum": [
      +          "relay",
      +          "decision"
      +        ],
      +        "type": "string"
      +      },
      +      "probe": {
      +        "description": "Shell command run from the repo root by clock_in; exit 0 answers the relay. Required on a relay, or nothing is written.",
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "q": {
      +        "description": "The question.",
      +        "minLength": 1,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "q",
      +      "kind"
      +    ],
      +    "type": "object"
      +  }
      +]
    • removedInput schema / properties / handoff_patch / properties / open_questions / items / minLength
      Removed value: -1
    • removedInput schema / properties / handoff_patch / properties / open_questions / items / type
      Removed value: -"string"
    • changedInput schema / properties / history_entry / properties / notes / description
      Previous value: -"Optional free text for the next session reading the ring."New value: +"Optional free text for the next session reading the ring, and recorded in the unit's attempt (#259): at most 2000 code points."
    • addedInput schema / properties / history_entry / properties / produced
      Added value: +{
      +  "description": "What the unit produced, [{kind, ref}] (a commit, a file, a run output). Recorded with the unit's attempt, and handed to every unit that depends on it as upstream[].last.",
      +  "items": {
      +    "properties": {
      +      "kind": {
      +        "description": "What sort of thing: commit, file, pr, run-output.",
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "ref": {
      +        "description": "Where it is: a SHA, a path, a URL.",
      +        "minLength": 1,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "kind",
      +      "ref"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  2. Changed7 schema fields changedv0.36.2
    • addedInput schema / properties / accounting / description
      Added value: +"Per-unit cost line, or null (the default) to log a line with no cost figures at all. A non-null line must carry `tokens` and `duration_ms`; `model` is required when job.roles names this unit's role. The object shape is described below."
    • addedInput schema / properties / checkpoint / description
      Added value: +"Path to the shift-work checkpoint JSON file (by convention .shiftwork/checkpoint.json in the repo the job works on), absolute or relative to the server's working directory. The same file shiftwork_plan, shiftwork_clock_in and shiftwork_clock_out take; it must validate against assets/schemas/shiftwork-checkpoint.json."
    • addedInput schema / properties / history_entry / properties / notes / description
      Added value: +"Optional free text for the next session reading the ring."
    • addedInput schema / properties / history_entry / properties / outcome / description
      Added value: +"What happened, in a few words: done, blocked on X, dropped because Y."
    • addedInput schema / properties / history_entry / properties / unit / description
      Added value: +"The unit this entry is about — normally the `unit_id` being clocked out."
    • addedOutput schema / properties
      Added value: +{
      +  "cursor": {
      +    "description": "plan.cursor after the move.",
      +    "type": "string"
      +  },
      +  "log": {
      +    "description": "Path of the <checkpoint>.log.jsonl the accounting line was appended to.",
      +    "type": "string"
      +  },
      +  "reason": {
      +    "description": "With result `error`: one sentence saying why the call was refused.",
      +    "type": "string"
      +  },
      +  "result": {
      +    "description": "`ok` when the checkpoint was rewritten; `error` with `reason` when the call was refused and nothing was written.",
      +    "type": "string"
      +  },
      +  "status": {
      +    "description": "The status it now holds.",
      +    "type": "string"
      +  },
      +  "unit": {
      +    "description": "The unit clocked out.",
      +    "type": "string"
      +  }
      +}
    • addedOutput schema / required
      Added value: +[
      +  "result"
      +]
  3. First observedv0.36.0

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already mark this as a non-read-only, destructive, non-idempotent operation, and the description adds substantial context: schema validation before a single atomic write, failure semantics with {"result": "error", "reason": ...}, cursor advancement rules, history-ring behavior, attempts truncation, and accounting gates. This gives the agent a clear picture of what is mutated and what happens on failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense and long, but it is front-loaded with the core action and every sentence carries operational detail for a complex 10-parameter mutation tool. It could be split into clearer sections, but it avoids filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of the tool, the nested schema, the annotations, and the presence of an output schema, the description is complete enough for correct invocation. It covers the atomic write model, required arguments, cursor rules, accounting constraints, follow-up creation, agreements, and role/model validation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters in detail. The description still adds useful cross-parameter meaning, especially the required handoff_patch/history_entry contract, cursor behavior, accounting-line contents, follow_ups and agreements effects, and the job.roles model gate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource: clock out a shift-work unit and record that it finished, then enumerates the concrete mutation effects. It explicitly distinguishes the sibling tools by saying shiftwork_clock_in briefs, this tool closes, and shiftwork_status reads what both wrote.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description says exactly when to call it: once per unit after its agent returns. It also names the related alternative tools and explains the unit_id prerequisite, including which units are accepted and which are refused.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.