# Architecture

SEED-agent is a single OpenCode plugin package. `src/index.ts` exports a `PluginModule`, and `server(input, options)` builds config, managers, tools, and hooks for the current OpenCode workspace.

Core flow:

```text
/seed command
  -> command.execute.before hook
  -> .seed-agent/runs/<run_id>/ artifacts
  -> Lab-Representative prompt and SEED tools
  -> trace/evidence/progress/report runtime
```

The primary OpenCode agent id for Lab-Representative is `lab-representative`.

Runs started through `/seed` default to `run_mode: "coordinator"`. In that mode
the root Lab-Representative session is expected to plan, dispatch, verify, and
report rather than directly implement substantial file changes. Direct native
edit/write/bash writes are blocked by `tool.execute.before` unless a one-shot
`seed_coordinator_direct_work` exception records the reason first. Ad hoc tool
runs remain `single_agent` unless explicitly created otherwise.

SEED registers one SEED OpenCode agent. Dynamic workers are represented by
role contracts in the accepted team spec, not by a fixed OpenCode subagent
roster. Child sessions run with the Lab-Representative agent id and receive
their durable member contract through the launch prompt and later context
injection.

The plugin does not replace OpenCode's chat runtime. It uses OpenCode's native agent config, command config, plugin tools, MCP config, and hook surfaces, then adds SEED-specific task and trace state.

Key modules:

- `src/config/`: JSONC config defaults, discovery, and merge.
- `src/agents/`: lab role prompts, permissions, and OpenCode agent config.
- `src/mcp/`: built-in MCP defaults and user-disabled MCP preservation.
- `src/tools/`: SEED tool registry.
- `src/features/run-state/`: run and task artifacts.
- `src/features/team-mode/spec.ts`: dynamic team spec contract, validation,
  write-lease checks, artifact persistence, and acceptance/rejection events.
- `src/features/model-call/`: OpenCode child-session creation, prompt/promptAsync
  dispatch, model fallback attempts, session reads, cancellation, and safe
  transcript compaction.
- `src/features/workspace-state/`: durable workspace intents, read/write/append
  leases, conflict records, stale base-hash detection, diff refs, lease
  enforcement helpers, and final-report completion blocking for unresolved
  conflicts or missing/out-of-scope write evidence.
- `src/features/document-ingestion/`: local PDF text extraction, extractor
  discovery, cross-platform normalization, page/chunk artifacts, and
  untrusted-document evidence reports.
- `src/features/observability/`: JSONL trace, model-call attempt stream,
  evidence ledger, and activity journal coordination.
- `src/features/idempotency/`: durable action keys for coordinator decisions
  and dispatches so repeated tool calls can be detected instead of relaunched.
- `src/features/redaction/`: observable-log redaction for token-looking values,
  secret env dumps, hidden reasoning markers, and private host paths.
- `src/features/trust-boundary/`: reusable wrappers for untrusted documents,
  browser/web content, clipboard text, and external model output.
- `src/features/safe-transcript/`: central visible-transcript formatter that
  omits hidden reasoning parts and counts redactions for summaries.
- `src/features/lab-meeting/`: manual durable feedback-gate state, meeting
  summaries, pause/resume state, internal-work launch gating, and a
  per-directory supervisor that marks stale background work and delivers queued
  messages while the plugin server is alive.
- `src/features/context-ledger/`: cache-aware context bundles, body refs,
  digests, render plans, and injection metrics for root, member launch,
  message delivery, compaction, and Lab Meeting resume turns.
- `src/features/trace-store/`: compatibility facade for older imports.
- `src/tools/executors/`: focused tool execution modules for shared adapters,
  delegation, and reporting.
- `src/plugin/`: OpenCode runtime hooks.

Dynamic team creation is contract-first. The Lab-Representative may synthesize
role ids and role labels for the current professor task, but SEED validates the
spec before launch: planner/executor/verifier coverage, capability profiles,
tool/MCP/skill policy, prompt budget, evidence obligations, human escalation,
and exclusive write paths are checked before any child session can be created.
Accepted specs are stored under `team/specs/` and indexed as run artifacts;
rejections are recorded in the trace and activity journal. `seed_team_create`
then records each capability profile as part of the member contract, creates a
`creating` runtime, sends each member a durable handoff message, and queues
bounded background child sessions through the same OpenCode session API path
used by delegation. The concrete OpenCode runner remains `lab-representative`;
the member's actual identity is its dynamic `role_id`, role label, mission,
tool policy, and evidence contract.

Team communication is also OpenCode-native. `seed_team_send` always writes a
durable inbox message first, then wakes an idle recipient session with
`promptAsync` when that member has a known child session and is idle or waiting
on mailbox work. Busy members keep the message as mailbox-only delivery.
`seed_team_broadcast` fans a single announcement out to multiple member inboxes.
Every message and acknowledgement is also appended to
`team/conversation.jsonl`, giving the professor and maintainer one chronological
debug surface for who said what to whom without scraping per-member inbox
folders.
When members need a shared decision surface, `seed_team_meeting_create`,
`seed_team_meeting_post`, `seed_team_meeting_status`, and
`seed_team_meeting_close` persist a thread under `team/meetings/` with agenda,
participants, decisions, action items, and close summary.
`experimental.chat.messages.transform` injects compact team context into the
Lab-Representative/root session and member sessions, while unread mailbox
messages and open participant meeting summaries are injected only into the
intended member session. Members are expected to acknowledge handled messages
with `seed_team_ack`, leaving the mailbox and meeting files as the durable
source of truth.
OpenCode `session.status`, `session.idle`, and `session.error` events update
the matching team member by child session id, so idle members with unread
mailbox work are shown as `waiting_on_mailbox` instead of looking silently
finished.

SEED keeps dynamic members as durable agent-threads and avoids repeatedly
injecting all shared context as raw prompt text. Stable member contracts, warm
run/team summaries, hot mailbox/meeting deltas, heavy artifacts, and memory
checkpoints are stored as context bundles under `context/`. Launch turns may
inline the stable contract once, while later turns receive compact digests or
body refs through a context injection plan. This is intentionally provider
neutral: SEED does not call a prompt-cache API directly, but it keeps each
agent-thread's fixed role/tool/skill prefix more stable and moves volatile
state into refs or short hot deltas.

Model calls are routed through `features/model-call` rather than embedded in the
tool registry. That manager handles OpenCode session create/get/list/messages,
sync and async prompts, model-route fallback attempts, aborts, and transcript
compaction. Reasoning parts are deliberately omitted from model-call summaries
and compact session messages. Delegation records immutable attempt ids such as
`agent_call_...-attempt-2` on every model attempt, marks stale attempts after a
fallback wins, and stores the current attempt id on the agent-call record so
debugging can separate retry history from current state.

Coordinator decisions and dispatches can carry idempotency keys. The
orchestrator loop claims those keys before appending a decision/tick event and
returns a deduplicated result when the same key appears again. Delegation also
records optional caller-provided idempotency keys and completes them with the
agent-call id and child-session metadata.

The activity journal has two layers. Raw chronological sanitized entries are
kept under `activity/raw/`, while `activity/<window>.md` and `activity/latest.md`
are regenerated as Lab-Representative briefings. Those briefings summarize the
latest interval in professor-facing language with current phase, active roles,
blockers, next action, prompt/document/workspace/evidence refs, redaction
counts, and a clear note that hidden model reasoning is not recorded.
Observable SEED trace records pass through redaction before they are appended to
events, agent-call, model-call, and tool-call JSONL files.

Lab Meeting Mode is manual in the professor UX. SEED stores meeting state in
`meetings/state.json`, but it does not automatically open a meeting on a
one-hour timer. `/seed-lab-meeting` requests background-session completion from
already-running workers, waits until those background sessions become terminal,
writes a meeting summary, sets the run to `awaiting_feedback`, and blocks new
delegation, team creation, and write/append workspace leases. The professor
then resumes by sending a normal feedback message in the Lab-Representative
chat; that message is recorded as feedback, persisted as professor memory, and
used to reschedule remaining work.
When the OpenCode plugin server starts, SEED registers one unref'ed supervisor
per workspace directory. The supervisor polls the active run at a bounded
interval for stale background-worker marking and bounded message delivery, but
it does not trigger Lab Meetings by deadline.
Professor guidance that should survive a single chat turn is stored outside the
run directory under `.seed-agent/memory/`. Lab Meeting feedback is saved as
`feedback` memory automatically. The message-transform hook injects the compact
`[SEED professor memory]` block only into root Lab-Representative sessions, not
ordinary dynamic worker turns.

Run budgets are durable safety limits rather than provider billing estimates.
SEED creates `.seed-agent/runs/<run_id>/budget.json` with runtime, child-session,
model-call, warning-ratio, and stale-worker thresholds. Delegation checks the
budget before launching a child session and records child sessions, model
attempts, and token usage after prompt results when OpenCode exposes token
metadata. The runtime supervisor also marks running background workers stale
when heartbeat/update time exceeds the configured threshold, which makes the
state visible in `/seed-status` and the trace before the professor has to guess
whether long work froze.
Context injection accounting is also folded into the budget file:
`usage.context.injected_chars_total`, category totals, and duplicate bundle
injection counts let `/seed-status`, `seed_context_metrics`, and continuation
snapshots reveal whether the team is repeatedly sending the same context instead
of using stable refs.
The same visibility data flows into continuation and activity artifacts:
continuation snapshots carry open sub-plans, budget state, stale workers, and
professor memory refs, while activity briefings include plan/verification,
budget, and stale-worker sections for quick professor inspection.
OpenCode remains the automatic compaction owner. SEED uses the host
`session.compacting` hook only to bridge durable lab-runtime context into the
compaction round: continuation summary, team roster, open meetings, member
contract/memory, unread mailbox, and background status. SEED does not replace
OpenCode token thresholds, summary generation, message pruning, or prompt
replacement.
Those compaction bridge entries are now rendered through the same context
ledger: memory can be bounded inline, stable contracts become digests, and
warm/hot runtime state is summarized with a body ref so the compaction prompt is
debuggable without turning every long artifact into repeated prompt text.

Workspace coordination is durable and path-based. Team members can declare
intents, acquire shared read leases, acquire exclusive write leases, use
append-only artifact leases, refresh or release leases, and resolve conflicts.
Overlapping exclusive writes and stale base hashes create open conflicts instead
of silently overwriting work. Write sub-plans bind granted lease ids before
execution and record workspace diff refs after native OpenCode edits or shell
writes. Worktree-isolated sub-plans can run without taking a main-workspace
lease, but SEED treats their output as evidence-only until Lab-Representative or
the declared merge owner records a worktree merge under a main-workspace write
lease. The merge record preserves both the isolated worktree diff ref and the
main-workspace diff ref in `workspace/merges.json`. Completed final reports are
blocked while workspace conflicts remain unresolved, required accepted write
sub-plans have no diff refs, recorded diff refs cannot be found, recorded
changed paths fall outside the sub-plan's declared `write_paths`, or required
worktree sub-plans lack an applied merge record. Lease conflicts can create a
team meeting thread so the decision and action items stay linked to the
conflict-resolution trail.

Document ingestion is separate from generic media inspection. `seed_look_at`
keeps the quick provenance and PDF metadata path, while `seed_document_extract`
uses `pdftotext` to create citable text, page, chunk, metadata, extractor, and
extraction-report artifacts under `artifacts/documents/<document_id>/`. The
extractor resolver honors `SEED_PDFTOTEXT_BIN` and `PDFTOTEXT_BIN`, checks PATH,
records binary flavor/version and OS details, and returns Linux-focused
installation guidance when extraction is unavailable. Extracted PDF text is
always marked as untrusted document evidence. This supports document/PDF tasks
when Lab-Representative chooses to use it; it is not an automatic domain
router.
The generic trust-boundary renderer wraps untrusted text with an explicit
instruction that the content is evidence, not operator instruction, and escapes
nested SEED trust-boundary sentinels before the excerpt is stored.

Team creation is task-agnostic. The core does not generate a document-review,
web-app, automation, or research-framework roster from keywords or file types.
Instead, Lab-Representative authors an inline team spec for the professor's
actual task, and the harness validates coverage, tool/MCP/skill policy, write
paths, evidence obligations, and launch policy before child sessions are
queued. Domain labels such as `workflow_template.name` may remain as spec
metadata for traceability, but they do not trigger core member generation.

Dynamic teams can evolve after launch. Lab-Representative should address
members by role id rather than child session id: `seed_team_member_followup`
wakes the existing child session when safe or records mailbox work when the
member is busy, and `seed_team_member_add` validates a new role contract before
launching another child session into the active runtime. Exclusive write-path
overlap is rejected unless the member contracts use an allowed serialization or
review policy.

Member session history is useful but not trusted as the only memory surface.
For every relevant member model call, SEED injects a compact `[SEED member
contract]` reminder from `team/runtime.json`; the same contract is appended to
OpenCode compaction context for member child sessions. The reminder includes
role id, mission, allowed/denied tools, MCP/skill policy, read/write paths,
evidence contract, stop condition, and report target.

Tool and workspace policy are enforced in `tool.execute.before` for member
child sessions. Member calls are checked against durable `allowed_tools` and
`denied_tools`, with only narrow coordination reads always available. Workspace
write/append intents and leases must stay within the member's persisted
`write_paths`; native write/edit/bash writes must also be covered by active
write or append leases before OpenCode executes them. Read-only workspace
access remains allowed for inspection.
Blocked calls are recorded as trace events and failed tool calls before OpenCode
executes the tool.

Planned subsystem boundaries:

- `src/features/browser-sidecar/`: persistent browser/media sessions,
  observation artifacts, ref maps, and policy-first interaction. See
  `docs/browser-media-sidecar.md`.
