Skip to content

app: workstream agent prompt and tools (proposals, drafts, its own pages) - #2289

Merged
AbirAbbas merged 16 commits into
workstreams/mainfrom
workstreams/wp10e-agent
Nov 3, 2026
Merged

AbirAbbas merged 16 commits into
workstreams/mainfrom
workstreams/wp10e-agent

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The Workstreams plan, WP-10 (engine side): a workstream's own agent gets a short workstream section in its system prompt and six tools that reach the workstream store through a narrow door the app injects. Every tool enforces the AI rules in code; a plain agent's belt and page are unchanged.

  • Door. session.WorkstreamDoor (internal/session/workstream_contract.go) is set by the app only on the conversation whose created-by is workstream (one line in engine.go beside cfg.Floor, one registration line in app.go). It resolves its workstream at each call by agent id (workstream.Store.ByAgent), because the agent is opened a moment before the workstream row exists. Task nodes, errands, quick tasks and question clarifications never carry it.
  • Prompt. Names the workstream (name, why) and says: it proposes and the person keeps; it writes only its own pages; it never invents numbers (live blocks instead); it starts nothing.
  • Tools (internal/session/workstream_tools.go):
    • workstream_read {what: workstream|pages|page|records|plan|goaheads, page_id?, kind?, state?}: wire-shaped JSON; numbers come from records and Factory reads, absent means unknown.
    • propose_decision / propose_question {text, source?}: a proposed record by agent; no path keeps it. Source defaults to the agent's chat; explicit sources are a member job, a page of this workstream or an http(s) URL.
    • propose_jobs {jobs: [{title, repo?, text?}], wave?, wave_title?, milestone_id?}: appends drafts only; existing waves, items and auto_start untouched; retries a version_conflict once, then reports. Never touches a Factory door.
    • propose_goahead {scope, factory?, job?, wave?, milestone_id?, cap_usd, ends_on, why}: the contract has no proposed go-ahead, so this is a proposed decision whose text begins Proposed go-ahead (not granted): ; it grants nothing.
    • write_agent_page {page_id?, title?, parent_id?, markdown, updated_from, source?}: creates or updates agent-owned pages only; a page the person owns is refused inside the store transaction and nothing changes; writes carry updated_by: agent, the store's time and updated_from.
  • Unknown argument fields (kept, state, cost_usd, auto_start, …) are refused before the door is touched.
  • Store. savePage refuses an agent write to a non-agent page in its transaction (ErrNotAgentPage); pagedoc.FromMarkdown imports markdown (live-block markers included) and the contract example round-trips byte for byte.
  • Spend. spend_usd already counts the agent conversation's recorded spend (spawned tasks fold into it). New: a workstream agent's settled turn sends workstream.changed (workstream part) so the spend is read again.

Validation contract

item tests
(a) a workstream agent gets the tools and prompt; a plain agent does not internal/session/workstream_tools_test.go, cmd/codeaf/appworkstreams_agent_test.go
(b) proposals are proposed records / drafts, never kept workstream_tools_test.go, appworkstreams_agent_test.go
(c) propose_jobs never calls the Factory internal/session/workstream_factory_test.go, appworkstreams_agent_test.go
(d) write_agent_page refuses a person's page and changes nothing (including the read/save race) internal/workstream/workstream_agent_test.go, workstream_tools_test.go
(e) agent pages carry the AI mark, time and source; history too internal/workstream/workstream_agent_test.go, appworkstreams_agent_test.go
(f) plan conflict retried once, then reported workstream_tools_test.go
(g) inputs cannot set counts, costs or states internal/session/workstream_validation_test.go, workstream_tools_test.go
(h) manual and laws TestTheManualMentionsTheWorkstreamAgentsTools, manual section in collections.md

Review and fix rounds

Two independent reviews. Fixed: the prompt's workstream brief is a non-blocking snapshot (no store read under the session lock); an agent's record and page writes check done inside their own transaction (the wire's unattributed record.propose keeps its old behaviour); imported list items always start with a paragraph; malformed inline markdown is parsed in linear time; repeated JSON keys are refused at any depth; the tools follow the session's argument laws (one decoder, marked refusals, no counting retry loop; cap_usd excused as dollars in its own file).

CI

PR gate: build, vet, gofmt, change entry and manual gates green. The remaining reds are the integration branch's baseline: internal/session complexity ledgers, prefix budgets, TestProgramHoldClassifiesEveryRegisteredChatTool, TestALandStartsNoAutomaticMaintenance; cmd/codeaf TestAppChatRowsR2SavedTurnTime, TestAppChatStartupR3QuietRelease, TestRecipeDoorWritesTheWorktreeWhenOnlyTheCheckoutIsHeld.

Contract gaps

  • No proposed state or record kind for go-aheads: proposals are decision records with a fixed text prefix; the person grants the real go-ahead.
  • No turn-level message source is reachable from inside the engine tool; the default source is the agent's conversation.

No wire shapes or handlers changed. Suggestions on the person's pages, autonomy-driven small changes and the watching agent are later work.

🤖 Generated with Claude Code

@AbirAbbas
AbirAbbas force-pushed the workstreams/wp10e-agent branch 2 times, most recently from 7263205 to eabc92d Compare November 3, 2026 07:33
AbirAbbas and others added 16 commits November 3, 2026 02:52
…mport

The store now refuses, inside the save transaction, an agent write to a page
the person owns, and an agent create of a page it would not own, so a page made
the person's between a read and a save is still refused. ByAgent finds the
workstream whose agent is a conversation. pagedoc.FromMarkdown turns markdown
into the editor JSON the page renderer reads, live-block markers included, and
the contract's example round-trips byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A conversation handed a WorkstreamDoor carries workstream_read,
propose_decision, propose_question, propose_jobs, propose_goahead and
write_agent_page, and a short prompt section naming its workstream and the
rules: it proposes and the person keeps, it writes only its own pages, it
never invents numbers and it starts nothing. Arguments outside each schema
are refused before the door is touched, propose_jobs only appends drafts and
retries a version conflict once, and a go-ahead proposal is a proposed
decision that grants nothing. Without a door the belt and the page are
unchanged, and task nodes, errands and clarifications never carry it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…turns

The app sets the workstream door on the conversation whose created-by is
workstream and on no other; the door finds its workstream at each call, since
the agent is opened a moment before the workstream row exists. Its writes are
attributed to the agent and send workstream.changed, and a workstream agent's
settled turn sends one too so its spend is read again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…port is linear

An agent's record or page write now refuses a done workstream inside the
same write transaction, so marking done between the agent's read and its
write leaves nothing behind. Imported list items always begin with a
paragraph, and unmatched backticks or brackets are consumed in one pass
instead of rescanning the rest of the line for each one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prompt's workstream brief is a snapshot that never touches the store, so
a re-render under the session lock cannot wait on it. Arguments decode only
through decodeToolArguments, a repeated key is refused at any depth, every
argument refusal is marked, and the plan's one retry is written without a
counting loop. A go-ahead's proposed cap is dollars, excused from the
whole-number law in its own file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tore

The app remembers each workstream's name and why by its agent when it creates,
reads, updates, marks done or reopens one, and when the agent's door resolves
it, so the prompt reads that snapshot. A done refusal from the store reaches
the agent as the workstream being done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the write

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hen done

The done check in ProposeRecord read the defaulted actor, so the wire's
unattributed record.propose on a done workstream, which has always landed as
a proposal, was refused. It now reads the attribution the caller gave, and the
agent's door keeps passing its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app shows the agent's summary on the Overview from the agent page titled
exactly "Where it stands". The prompt and write_agent_page now name that page
and ask the agent to keep it current with what it was updated from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t budget

The rule now fits in one short line, and the workstream section's own budget
test allows 1000 bytes for the worst-case name and why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…save

The store now refuses an agent write to a page the person owns, so the seed
that reproduces the contract's agent-saved Overview hands the page to the agent
for that save and back again, keeping the agent's attribution and version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts pages

The store now refuses an agent write to a page the person owns, so the
version-read and deleted-tree fixtures that save or create as the agent make
those pages the agent's first; what they assert is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the workstreams/wp10e-agent branch from eabc92d to f4ac14a Compare November 3, 2026 07:53
@AbirAbbas
AbirAbbas merged commit 4dcd515 into workstreams/main Nov 3, 2026
3 of 8 checks passed
@AbirAbbas
AbirAbbas deleted the workstreams/wp10e-agent branch November 3, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant