Skip to content

Latest commit

 

History

History
314 lines (229 loc) · 12.7 KB

File metadata and controls

314 lines (229 loc) · 12.7 KB

ipman v2.1 — human ergonomics

ipman v2.1 is purely additive. The JSON protocol is unchanged from v2.0: same envelope, same operations, same fields. What v2.1 adds is a thin CLI veneer over that protocol so a supervising human can run the most common transitions without hand-rolling JSON.

If your code talks to ipman through the JSON envelope, nothing in this release changes your wire. Skip to No-goals and Migration patterns if you want to know what ipman deliberately did not do — and how the new CLI maps onto the v2 ops you already use.


What's new at a glance

Group Verb Serializes to
Context ipman --activate <plan> plan.activate
Context ipman --current <task-or-phase> task.set_current or phase.set_current (auto-detected)
Read view ipman -N / ipman --next composite read of workspace.context_get + plan.get + phase.get + task.get + instruction.list (×3 scopes) + task.list
Lifecycle ipman --start <task> task.transition (status: in_progress)
Lifecycle ipman --close <task> … task.close (with optional structured evidence and git auto-capture)
Lifecycle ipman --cancel <task> … task.cancel
Lifecycle ipman --defer <task> … task.defer
Modifier --dry-run prints the would-be JSON envelope to stdout and exits without touching the DB

Every verb resolves selectors the same way: numeric id, uid (e.g. task_42), or label (resolved against the active plan).


The surface, by example

Each subsection shows the shell form on the left and the exact JSON envelope ipman would send on the right (captured via --dry-run). The point is: there is no parallel mutation path. The CLI verb produces the same JSON your agent already uses.

Activate a plan

ipman --activate ship-login-refactor
{"op":"plan.activate","params":{"id":1}}

--activate accepts id, code, uid, or label and resolves to the plan's id before dispatch. Same as the JSON op accepts directly.

Set the current task or phase

ipman --current move-jwt-verification
{"op":"task.set_current","params":{"id":42}}
ipman --current auth-rewrite
{"op":"phase.set_current","params":{"id":3}}

The verb auto-detects whether the selector points to a task or a phase (the resolver rejects plans — use --activate for plans).

Inspect everything in one call

ipman --next

This runs no mutation. It calls workspace.context_get to find the active plan and cursors, fetches the plan/phase/task records, lists instructions at every scope (workspace, plan, phase), and lists pending tasks. The output is a stack of box-drawn tables. Use it as the session entry-point — one keystroke replaces six JSON envelopes.

Start a task

ipman --start review-pr-42
{"op":"task.transition","params":{"id":42,"status":"in_progress"}}

Close a task

The minimal form:

ipman --close review-pr-42 --summary "Approved" --comment "LGTM"
{"op":"task.close","params":{
  "id":42,
  "outcome_summary":"Approved",
  "closing_comment":"LGTM",
  "commit_sha":"791a7a0…",
  "dirty":false,
  "files_changed":["README.md"]
}}

When the CLI runs inside a git work tree, it shells out to git rev-parse HEAD, git status --porcelain, and git diff --name-only HEAD~1 and attaches the results as commit_sha, dirty, and files_changed. Outside a repo, those fields are silently absent. To override or suppress:

Flag Effect
--commit <sha> Override commit_sha (works outside a repo too)
--files a,b,c Override files_changed with a comma-separated list
--no-git Skip auto-capture entirely; no git fields sent

The override flags exist so a closure record can attribute work to a specific commit even when the closure happens later or from a different checkout.

Close with structured evidence

ipman --close review-pr-42 \
  --summary "Approved with two small tweaks" \
  --comment "Caller updated the cache key" \
  --validation "make test:passed" \
  --validation "shellcheck scripts/:passed" \
  --decision "Defer the rename to a follow-up — out of scope here"
{"op":"task.close","params":{
  "id":42,
  "outcome_summary":"Approved with two small tweaks",
  "closing_comment":"Caller updated the cache key",
  "validations_run":[
    {"cmd":"make test","status":"passed"},
    {"cmd":"shellcheck scripts/","status":"passed"}
  ],
  "decisions":[
    "Defer the rename to a follow-up — out of scope here"
  ]
}}

Both flags are repeatable. --validation splits on the last colon, so commands containing colons (URLs, paths) parse correctly: --validation "curl https://example.com:200" becomes {"cmd":"curl https://example.com","status":"200"}. The passed/failed convention is documentation, not enforcement — status is any non-empty string.

validations_run and decisions are surfaced by closure.get and rendered in the per-plan markdown (ipman --render) so future agents resume with the evidence intact, not just the prose.

Cancel a task

ipman --cancel migrate-redis-cluster \
  --summary "Out of scope after planning review" \
  --comment "Deprioritized; tracked in the next plan"
{"op":"task.cancel","params":{
  "id":17,
  "resolution":"canceled",
  "outcome_summary":"Out of scope after planning review",
  "closing_comment":"Deprioritized; tracked in the next plan"
}}

task.cancel writes resolution=canceled; task.close writes resolution=completed. Use the right one — they mean different things in the audit trail.

Defer a task

ipman --defer migrate-redis-cluster \
  --reason-text "Blocked on infra ticket INFRA-2031" \
  --reason-code external_dependency
{"op":"task.defer","params":{
  "id":17,
  "reason_text":"Blocked on infra ticket INFRA-2031",
  "reason_code":"external_dependency"
}}

deferred is not a terminal state. Resume the task later with --start (which transitions back to in_progress).

Preview without committing

--dry-run is composable with every write verb. It prints the JSON envelope that would be sent to stdout, then exits with code 0. No DB mutation, no event, no closure record:

ipman --close review-pr-42 \
  --summary "..." --comment "..." --validation "make test:passed" --dry-run

Use it to script-review the wire before sending it, to teach the mapping to a human reviewer, or to capture a request shape for testing.


No-goals

The point of v2.1 is to make supervision faster without changing the data model the agents have been using since v2.0. These were considered and explicitly rejected:

1. No reintroducing uid/label/code as protocol selectors

Selectors on the wire stay id-only. uid and label are accepted on the CLI as a convenience — the resolver translates them to id before dispatch — but the JSON envelope never carries them. This preserves the v2.0 invariant that names cannot silently mutate under a request (see v2-migration.md for the full rationale).

The cost: a CLI selector can be ambiguous (the same label may exist in a different plan than the one currently active). The CLI handles that with explicit error messages; the protocol stays unambiguous.

2. No task.complete_current or other context-implicit ops

Every write op still takes an explicit id. There is no "close whatever the cursor points at" shortcut, on the wire or in the CLI. The reasoning: cursor-implicit mutations are convenient until two agents share a workspace, at which point a misaligned cursor silently writes to the wrong task. v2.1 keeps the cursor a navigation concept, not a mutation target.

If you want the convenience locally, you already have it: ipman --close $(ipman --next | grep current-task | awk …) is a shell composition, not a protocol primitive.

3. No process runner

ipman doesn't shell out to run your commands. --validation "make test:passed" records that you ran make test and it passed; ipman never runs make test for you. The only shell-outs ipman makes are the three read-only git invocations that back --close auto-capture, and they're suppressible with --no-git.

This is a deliberate refusal. Once a planner runs commands, it owns their failure modes (timeouts, env, secrets, exit-code semantics, retry policy). v2.1 stays a planner.

4. No "soft delete" or hidden state

Closed, canceled, deferred — every terminal transition writes a closure_record. There is no flag to suppress evidence capture, no way to write "I closed this without saying why". task.close and task.cancel require outcome_summary and closing_comment; the CLI flags are required for the same reason.


Migration patterns

If you've been hand-rolling JSON envelopes against v2.0, you don't have to change anything. The CLI is an alternative, not a replacement. But here's what each pattern collapses into when you adopt the new surface.

Starting a task

- echo '{"protocol_version":2,"request_id":"r1","actor":"agent",
-        "op":"task.transition",
-        "params":{"id":42,"status":"in_progress"}}' | ipman
+ ipman --start 42

If you prefer a label:

- # First lookup the id, then transition
- echo '{"op":"task.lookup","params":{"label":"review-pr-42","plan_id":1}}' | ipman
- echo '{"op":"task.transition","params":{"id":42,"status":"in_progress"}}' | ipman
+ ipman --start review-pr-42   # resolved against the active plan

Closing with a closure record + git context

The hand-rolled v2.0 form was already verbose; v2.1 collapses the boilerplate:

- SHA=$(git rev-parse HEAD)
- DIRTY=$(git status --porcelain | grep -q . && echo true || echo false)
- echo "{\"op\":\"task.close\",\"params\":{
-   \"id\":42,
-   \"outcome_summary\":\"Approved\",
-   \"closing_comment\":\"LGTM\",
-   \"commit_sha\":\"$SHA\",
-   \"dirty\":$DIRTY
- }}" | ipman
+ ipman --close 42 --summary "Approved" --comment "LGTM"

The CLI captures the same git fields automatically when run inside a work tree.

Closing with structured evidence

- echo '{"op":"task.close","params":{
-   "id":42,
-   "outcome_summary":"...","closing_comment":"...",
-   "validations_run":[
-     {"cmd":"make test","status":"passed"},
-     {"cmd":"shellcheck scripts/","status":"passed"}
-   ],
-   "decisions":["Defer the rename to a follow-up"]
- }}' | ipman
+ ipman --close 42 --summary "..." --comment "..." \
+   --validation "make test:passed" \
+   --validation "shellcheck scripts/:passed" \
+   --decision "Defer the rename to a follow-up"

Setting the workspace cursor

- echo '{"op":"plan.activate","params":{"id":1}}' | ipman
- echo '{"op":"phase.set_current","params":{"id":3}}' | ipman
- echo '{"op":"task.set_current","params":{"id":42}}' | ipman
+ ipman --activate 1
+ ipman --current 3       # auto-resolves to phase
+ ipman --current 42      # auto-resolves to task

Session entry-point

- echo '{"op":"workspace.context_get","params":{}}' | ipman
- echo '{"op":"plan.get","params":{"id":1}}' | ipman
- echo '{"op":"phase.get","params":{"id":3}}' | ipman
- echo '{"op":"task.get","params":{"id":42}}' | ipman
- echo '{"op":"instruction.list","params":{"scope":"workspace"}}' | ipman
- echo '{"op":"instruction.list","params":{"plan_id":1}}' | ipman
- echo '{"op":"instruction.list","params":{"phase_id":3}}' | ipman
- echo '{"op":"task.list","params":{"plan_id":1,"status":"todo"}}' | ipman
+ ipman --next

--next runs the same ops; the CLI just composes them and renders the result as tables.


What did NOT change

  • Protocol version: still 2. No protocol_version: 3 exists.
  • Operation set: unchanged by v2.1. Current builds expose the operation count reported by .ipman/manifest.json; v2.1 itself added zero protocol ops and existing ops gained two optional fields each (see migrations 0002 and 0003).
  • Selector semantics on the wire: id-only on every non-lookup op.
  • Closure record fields: outcome_summary, closing_comment, lessons_learned, open_items_summary, followup_needed — all unchanged. v2.1 added validations_run and decisions as siblings, never replacing them.
  • Error codes and exit-code split: validation_failed, not_found, conflict keep their v2.0 semantics.
  • Storage layout: .ipman/ directory and SQLCipher-encrypted ipman.db are unchanged. Workspaces created on v2.0 work on v2.1 after the schema migrations apply on first open.

If you have an existing v2.0 client, it keeps working. The only thing v2.1 requires is the migrations 0002 (extend closures with git fields) and 0003 (extend closures with structured evidence) applying on first open — both are append-only column additions and don't break readers.