Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
278cf0d
Add agent definitions as data (internal/agent/definition)
mchalapuk Aug 8, 2026
a6e09e1
Add the explorer sub-agent definition and protocol
mchalapuk Aug 8, 2026
b4b52a1
Give the event stream an agent dimension, and render it in both front…
mchalapuk Aug 8, 2026
ed8c7aa
Add Budget: a shared ceiling on round-trips and tokens across an agen…
mchalapuk Aug 8, 2026
a4327a2
Add ADR for the event stream's agent dimension
mchalapuk Aug 8, 2026
59329cf
Add the Spawn tool: the harness can now actually spawn sub-agents
mchalapuk Aug 8, 2026
30ca456
Give every turn exactly one outcome (EventTurnEnded replaces EventTur…
mchalapuk Aug 8, 2026
ce47629
Map turn outcomes to -p exit codes, and feed the previous one forward
mchalapuk Aug 8, 2026
364e927
Add Situation and Scheme/Pass as data, ahead of a multi-pass turn loop
mchalapuk Aug 8, 2026
9426b35
Add ADR for narrowing-only agent definitions
mchalapuk Aug 8, 2026
383f5cf
Extend cmd/promptdump to cover explorer, and document multiple dumps
mchalapuk Aug 8, 2026
e809f70
Add an opt-in planning pass: goal/todo/delegate stated once, injected…
mchalapuk Aug 8, 2026
9829a1b
Add FinalOutcome: a final answer with open items must not read as Com…
mchalapuk Aug 8, 2026
f0ab06e
Replace Event.Path with self-stamped AgentName/AgentDepth
mchalapuk Aug 9, 2026
19b018f
Record the implementing commit in the event-stream-agent-dimension ADR
mchalapuk Aug 9, 2026
e1790a5
Replace narrowing-only agent definitions with a singleton permission …
mchalapuk Aug 17, 2026
f426cc9
Stop bracketing the permission mode around a spawn
mchalapuk Aug 17, 2026
6cdc597
Delete Definition.PermissionMode and Tighten
mchalapuk Aug 17, 2026
4d3c136
Re-justify ResolveTools now that narrowing-only agent defs are gone
mchalapuk Aug 17, 2026
a11d43d
Record the implementing commit in the singleton-permission-mode ADR
mchalapuk Aug 17, 2026
bbebf0d
Store the previous-turn-outcome note as history instead of re-injecti…
mchalapuk Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions CODED.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,22 @@ make # run before considering a change done
go test ./internal/foo/... # target just the changed package while iterating
```

`make` regenerates `prompts/agent-run.txt` alongside everything else. Review
that diff whenever a change touches the system prompt, a tool's
description/schema, or the agent loop's reminder/formatting logic — it's
`make` regenerates every file under `prompts/` alongside everything else —
one file per shipped agent definition (`agent-run.txt` for `main`,
`explorer-run.txt` for `explorer`, and so on as more definitions ship).
Review that diff whenever a change touches a system prompt, a tool's
description/schema, or a protocol's reminder/formatting logic — an
unexpected diff in a dump you didn't mean to touch usually means a shared
piece (a tool description, `agent.DefaultIdentity`, `internal/agent/protocol`'s
generated text) changed underneath more than one definition. Every dump is
meant to be committed alongside the code that produced it.

## Layout

- `cmd/coded` — CLI entry point, flag parsing, UI mode selection.
- `cmd/promptdump` — dev tool that drives a scripted fake conversation
through the real agent loop/tools/permissions to produce
`prompts/agent-run.txt`.
through the real agent loop/tools/permissions, once per shipped agent
definition, to produce `prompts/*.txt`.
- `internal/agent` — the turn-loop primitives (UI-agnostic): conversation
state, `<user-message>`/`<harness-message>` wrapping, provider-stream
accumulation into a single assistant Message, tool dispatch. No loop of
Expand Down Expand Up @@ -72,8 +77,8 @@ meant to be committed alongside the code that produced it.
- `internal/oneshot` — plain-text renderer for `-p` / non-interactive mode.
- `internal/render` — turns `agent.Event` error/retry payloads into short,
user-facing text shared by both renderers.
- `adr/` — architecture decision records; `prompts/agent-run.txt` — the
committed prompt dump.
- `adr/` — architecture decision records; `prompts/` — one committed prompt
dump per shipped agent definition (see `cmd/promptdump`).

## Conventions

Expand Down
14 changes: 7 additions & 7 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ consequences.

### Agents

- **Agent** — a named, configured participant: an identity, a tool subset, a model, a thinking scheme, a permission posture. v0.1 has exactly one, unnamed and implicit; from v0.2 the main agent is just the default one. *(v0.2 for the plural)*
- **Agent definition** — the file that declares an agent, in Claude Code's markdown-with-frontmatter format, with coded's extensions namespaced under a `coded:` key so the file stays valid for both. Narrowing-only on anything security-relevant: it can subtract tools and tighten permission mode, never widen either. *(v0.2)*
- **Agent** — a named, configured participant: an identity, a tool subset, a model, a thinking scheme. v0.1 has exactly one, unnamed and implicit; from v0.2 the main agent is just the default one. *(v0.2 for the plural)*
- **Agent definition** — the file that declares an agent, in Claude Code's markdown-with-frontmatter format, with coded's extensions namespaced under a `coded:` key so the file stays valid for both. It declares no permission posture of its own: the mode is the session's, shared by the whole tree. *(v0.2)*
- **Sub-agent** — an agent invoked by another agent through the `Spawn` tool, running to completion in an isolated conversation and returning only its final answer. Its entire life is one turn: one input — which is its goal — its own iterations, one final answer, one outcome. Fresh context is the point: the parent gets the conclusion, not the tool traffic. *(v0.2)*
- **Thinking** — reasoning the harness can see, require, check, and reuse: written in-band as tagged sections of an ordinary response, structured by a scheme, enforced on every turn. The invariant is about dependence, not prohibition — a provider's own extended thinking may pass through and be rendered, but **no scheme's correctness may rely on it, and every scheme must hold on a model that has none.** Off by default, because with it on the model does its real reasoning in a block that cannot be inspected, composed, or fed to a later pass, leaving the half that can as a summary of the half that mattered. That is the whole reason Pass and Protocol exist rather than a `thinking: high` knob.
- **Thinking scheme** — how one agent's turn is structured: an ordered list of passes. A single-pass scheme is the default and is exactly today's behavior; self-ask is two. This is the concept Claude Code's agent format cannot express, and the reason coded's format extends it. *(v0.2)*
Expand All @@ -44,7 +44,7 @@ consequences.
- **Step** — one unit of a multi-step tool call (a command in a Bash batch), permissioned, executed, and streamed independently.
- **Subject** — the structured description of a call that rules match against: a path's segments, a command's words. The tool decides how its own arguments decompose and reports **only facts about that shape** — never a judgment about which generalizations are safe to grant. That judgment is policy and lives entirely in the permission engine.
- **Rule** — an allow or deny decision scoped to a tool and optionally to its arguments (`Bash(git diff*)`). Read from settings, hot-reloaded on change, and extendable at the approval prompt.
- **Permission mode** — the session's default posture when no rule matches: `default`, `accept-edits`, `bypass`, `plan`.
- **Permission mode** — the session's default posture when no rule matches: `default`, `accept-edits`, `bypass`, `plan`. Exactly one is live at a time, set by the harness and shared by every agent in the tree.
- **Project root** — a boundary, not a preference: a call reaching outside it asks even under `bypass`, and is therefore denied under `-p`.

### Architecture
Expand Down Expand Up @@ -195,8 +195,8 @@ Goals:

- [ ] Markdown-with-frontmatter agent definitions loaded from `.coded/agents/*.md` (project), `~/.coded/agents/*.md` (global), and `.claude/agents/*.md` (compatibility), most-specific wins on name collision.
- [ ] Claude Code's field set works unchanged — `name`, `description`, `tools`, `model` — so an existing `.claude/agents` directory is usable as-is; every coded extension lives under a single `coded:` key so a definition coded understands stays valid for Claude Code, and unknown keys are ignored rather than rejected.
- [ ] `coded:` extensions: `scheme` (below), `permission_mode` (narrowing only), `max_iterations`, `budget`, `context` (which project docs and files the sub-agent is seeded with), and `checklists`.
- [ ] Definitions are **narrowing-only** on anything security-relevant: an agent's `tools` list can subtract from the registry but never add to it, and its `permission_mode` can only be stricter than the session's. An agent file is checked into a repo and arrives with the code — it must not be able to widen what the user approved.
- [ ] `coded:` extensions: `scheme` (below), `max_iterations`, `budget`, `context` (which project docs and files the sub-agent is seeded with), and `checklists`.
- [ ] Definitions declare **no permission mode**: one mode, set by the harness, applies to every agent in the tree, and nothing switches it around a spawn. An agent's `tools` list selects from what the harness registered and cannot introduce a tool the binary doesn't have.
- [ ] Built-in definitions shipped in-binary (`main`, `reviewer`, `explorer`), overridable by a same-named file on disk, so the feature has real users on day one and the format is exercised by our own agents.
- [ ] `coded agents list` / `/agents` shows what's loaded, from where, and what each one is allowed to do.

Expand Down Expand Up @@ -266,7 +266,7 @@ tool is that same pattern with a different payload.

- [ ] A built-in `Spawn` tool that invokes a named agent with a prompt, runs it to completion in an isolated conversation, and returns its final answer as the tool result. Named for what it does rather than after Claude Code's `Task`: an invocation is exactly one turn of a fresh agent, and "task" would name a rung of the work ladder we already have a word for. Compatibility is about the agent-definition file format, not tool names. Fresh context is the point: the parent gets the conclusion, not the sub-agent's tool traffic.
- [ ] `agent.Event` gains an agent path so nested activity is attributable, plus `EventAgentStarted`/`EventAgentComplete`. The TUI renders a sub-agent as a collapsible group; `-p` renders it indented. This is an ADR: the event stream stops being flat.
- [ ] One permission engine for the whole tree. A sub-agent never gets its own rule set, prompts serialize globally (one question on screen at a time, whoever asked), and an "always" answer applies session-wide as it does today.
- [ ] One permission engine for the whole tree. A sub-agent never gets its own rule set or its own mode, prompts serialize globally (one question on screen at a time, whoever asked), and an "always" answer applies session-wide as it does today.
- [ ] Sub-agents run **sequentially** in v0.2, but the event stream, the permission serialization, and the session layout are designed for concurrency so parallelism is a scheduler change and not a rewrite. djinni set `DevPhase.parallel` and never read it; we're not shipping the flag before the scheduler.
- [ ] Session persistence nests: a sub-agent transcript is its own file referenced by the parent's, so `coded sessions list` still lists conversations and not fragments, and a run can be replayed in full.
- [ ] Budgets: a per-invocation ceiling on round-trips and tokens, inherited and decremented down the tree, surfaced in the usage banner per agent. djinni's only bounds were 20 rounds and 20 consecutive build failures, with no cost ceiling anywhere — this is the fix for that.
Expand Down Expand Up @@ -333,7 +333,7 @@ Features:

## Cross-cutting, throughout

- [ ] ADRs for the decisions this roadmap makes: reasoning being explicit and enforced rather than delegated to a provider's own thinking, intent being explicit too (goal, todo list, and a declared turn outcome), the event stream gaining an agent dimension and a turn outcome, agent definitions being narrowing-only, the archetype living in the repo as data, workflow stages being derived from the archetype rather than restating it, checkpoint commits being opt-in, and enforcement being path-scoped.
- [ ] ADRs for the decisions this roadmap makes: reasoning being explicit and enforced rather than delegated to a provider's own thinking, intent being explicit too (goal, todo list, and a declared turn outcome), the event stream gaining an agent dimension and a turn outcome, one permission mode covering the whole agent tree, the archetype living in the repo as data, workflow stages being derived from the archetype rather than restating it, checkpoint commits being opt-in, and enforcement being path-scoped.
- [ ] `cmd/promptdump` grows with each milestone — every agent, scheme, pass, and archetype rendering ends up in a committed dump, because a system whose behavior is prompts needs its prompts in the diff.
- [ ] Per-agent and per-stage token accounting on the existing usage banner, and a run summary at the end of a workflow.
- [ ] Every new subsystem — schemes, archetypes, workflows — is loaded from data with a built-in default, and a malformed file keeps the previous good state instead of crashing, matching how permission rules already hot-reload.
Expand Down
3 changes: 3 additions & 0 deletions adr/2026-07-01_00-38-09_ui-agnostic-agent-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ events carrying a response channel, not callbacks into a UI.
- The stream is the only path to a front end, so anything appended to history
without emitting events is invisible to the UI by construction —
`agent.SyntheticReadmeExchange` is the one deliberate case.
- A sub-agent's activity is still just events on this same stream, not a
parallel one a consumer has to know how to multiplex — see
[The event stream gains an agent dimension](2026-08-08_03-48-52_event-stream-agent-dimension.md).
63 changes: 63 additions & 0 deletions adr/2026-08-08_03-48-52_event-stream-agent-dimension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# The event stream gains an agent dimension

- **Status:** Accepted
- **Date:** 2026-08-08 03:48:52
- **Commit:** `f0ab06e`

## Context

[UI-agnostic agent loop driven by an event stream][agent-loop-adr]
established `agent.Event` as the only path from the turn loop to a front
end. Once a turn can spawn a sub-agent — all tool calls, permission prompts,
and text are all still events on the *same* stream a flat consumer already
drains, with nothing in the `Event` shape saying which agent in the tree
produced any of them.

Two designs were available: give a spawned agent its own, separate event
channel that the parent explicitly multiplexes, or keep one channel and
tag each event with where it came from. A separate channel is how a naive
`Spawn` implementation reads at first (a sub-agent's `Run` already returns
one), but it means every consumer — both front ends today, anything else
later — has to know about the tree shape and multiplex channels itself.
It also doesn't compose: a grandchild's channel would need multiplexing
into its child's, which multiplexes into the parent's, duplicating the
same fan-in logic at every level.

[agent-loop-adr]: 2026-07-01_00-38-09_ui-agnostic-agent-loop.md

## Decision

Every `Event` gains two fields, set once by whichever agent produced it:

* `AgentName string`, the full chain from the outermost agent to this one
joined by `/` (`"main"` for the root, `"main/explorer"` for a direct child,
and so on for deeper nesting),
* `AgentDepth int`, that chain's length minus one (`0` at the root).

An `Agent` now carries its own `Name`/`Depth`, fixed at construction --
`"main"`/`0` for the one root `Agent` `cmd/coded` builds, or the calling
agent's `Name`+`"/"`+the new definition's `Name` / calling agent's
`Depth`+`1` for a child `definition.NewChild` builds.
`Agent.Run` wraps its internal event channel in a single relay before
returning it, copying `Name`/`Depth` onto every event the channel carries.

Because a child's own `Run` call already stamps every event with its full
chain before `Spawn` ever sees it, `agent.ForwardChild` just relays without
rewriting anything.

## Consequences

- The stream stays flat and single-channel, which is what the previous
record's testability argument (a fake provider and a channel drain, no
terminal involved) depends on — that continues to hold for a tree of
agents exactly as it did for one.
- A consumer that ignores `AgentName`/`AgentDepth` still works: every
event still arrives, in order, on the one channel it already reads.
- `AgentName`/`AgentDepth`-aware rendering is still each front end's own
problem to solve well.
- `AgentDepth` being its own field means indentation-by-depth never has
to infer depth from `AgentName`, and nothing has to parse `AgentName`
back apart to find the innermost name either — the two questions ("how deep"
and "which agent") are answered by two fields instead of one being
overloaded for both.

85 changes: 85 additions & 0 deletions adr/2026-08-10_01-53-43_singleton-permission-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# One permission mode for the whole agent tree

- **Status:** Accepted
- **Date:** 2026-08-10 01:53:43
- **Commit:** `6cdc597`

## Context

[Four permission modes with risk-based fallback][modes-adr] gave a session one
posture for calls no rule matches. v0.2 adds agent definitions and `Spawn`, and
with them a question that posture never had to answer: when a parent spawns a
child, whose mode applies?

Two designs were available.

**A permission mode per agent definition.** `Definition` carries a
`permission_mode` alongside its identity, tool subset and scheme, and a spawned
child runs under its own posture rather than its parent's. A read-only
investigator can then be pinned to `plan` regardless of how loose the session
around it is. Because a definition is authored data, this design also has to
decide what happens when a definition declares a mode *looser* than the session's:
either the declaration is combined with the parent's so that only the stricter
of the two survives, or a widening declaration raises a permission prompt the
way a tool call does.

**One mode for the session.** The harness owns a single mode, resolved from the
flag and config layers at startup and changeable by the user at any point after.
Every agent in the tree consults that one value, an agent's definition says
nothing about permissions at all, and a spawn does not change the posture in
force.

[modes-adr]: 2026-07-01_00-38-09_permission-modes.md

## Decision

Permission mode belongs to the session. There is exactly one value in force at
any moment: the harness resolves it from the flag and config layers at startup,
and from then on it changes only in response to explicit user input about the
permission mode — never on its own, and never as a side effect of anything the
agent tree does. Such a change applies immediately to every agent, the ones
already running as much as the ones spawned afterward. `Definition` carries no
permission mode, and a spawn does not change the value.

Per-definition mode was rejected on four counts.

- **Neither answer to the widening question is good.** Keeping the stricter of
the two discards a declaration silently, in exactly the case its author cared
about — and it guards one authoring surface while `.coded/settings.json` sets
the mode outright at higher precedence (see
[Layered config with a fixed merge order][config-adr]), which is not a
boundary. Prompting on escalation asks for unbounded authority mid-turn, where
refusing kills the whole sub-agent rather than one call.
- **It brackets the mode around control flow.** The user changing the mode is
one writer, no nesting, the new value in force from then on. A per-agent mode
installs on spawn and *restores* on completion, so what applies depends on
where in the tree execution is — sound only while spawns are sequential, since
two overlapping children would restore over each other.
- **Its demonstrated uses belong elsewhere, or are wrong.** `explorer`'s `plan`
is belt-and-braces over its own read-only tool list, which a test checks
earlier and more cheaply — plus a reluctance to prompt on a sub-agent's
behalf, which has it backwards: under a manual mode the user wants to be asked
about what a sub-agent does, exactly as about what the main agent does.

[config-adr]: 2026-07-01_00-38-09_config-merge-order.md

## Consequences

- Parallel sub-agents get cheaper rather than harder. The mode stays one value
that every agent reads and only the user writes, so concurrent children all
observe the same posture — and a change the user makes mid-run reaches all of
them at once, which is the behavior to want.
- Per-agent prompt posture becomes inexpressible. The mode is the fallback for
calls no rule covers, so "this agent may edit, but always ask" had a spelling
and now has none; the remaining answer — don't grant the tool — turns a middle
setting into all-or-nothing.
- Sub-agent operations are prompted for, and that is the intent. Under a manual
mode an unmatched call raises a prompt wherever in the tree it came from, and
the user answers it the way they answer the main agent's. Making a sub-agent
quieter than its parent is not a goal the mode is asked to serve; what remains
open is only presentation — which agent is asking, and one question at a time
once spawns run concurrently.
- A `permission_mode` key in an agent file (supported by Claude Code) is now
an unknown key, and unknown keys are ignored rather than rejected — so a
definition written against a harness that honors one does less than its author
intended, silently.
Loading