AGENTS.md
packages/senpi-task/AGENTS.mdAGENTS.md
Quality
89/100
Scores the file, not the repository.Length
1,733 words
14 headings · 1 code blocksRepository
67k
— · pushed 0 days agoLast changed
2 days ago
First indexed 2 days ago.1# senpi-task - Senpi Task State Machine + Tool Surface23**Generated:** 2026-07-17 / 7d664b96b45## OVERVIEW67The Senpi-coupled engine behind the `omo-senpi` task component: a durable task state machine, a persistent record store, two child runners (in-process and RPC process), a residency/TTL/reconcile lifecycle, an exactly-once completion notifier, a steering engine, a named-team runtime, and the 4 task + 7 lead-team `ToolDefinition`s. Package: `@oh-my-opencode/senpi-task` (private, `sideEffects: false`). `@code-yeongyu/senpi` and `typebox` are optional peers (`package.json:25`) so pure state/store/schema code stays runnable without a live Senpi import; runner and tool code that needs the Senpi surface is isolated. Do not import `packages/omo-opencode` from here.89## ANATOMY1011| Area | Path | Purpose |12|------|------|---------|13| State machine | `src/state/` | `TaskStatus` (7: `pending`/`running`/`completed`/`error`/`cancelled`/`interrupted`/`lost`) and `ResidencyState` (5) enums, `TaskRecord`, and `transitionTaskRecord` with late/invalid-transition audits (`state/types.ts`, `state/transitions.ts`). |14| Store | `src/store/` | `createTaskRecordStore` JSONL record store with an in-memory read cache (mtime+size validated; `list()` prunes entries whose files vanished on disk) and a capped (16) LRU append-fd pool reusing open JSONL log handles; `resolveStateDir` (`<project_dir>/.omo/senpi-task` default, `store/state-dir.ts:6`), redaction, and the security test. |15| Runners | `src/runners/` | `InProcessRunner` (shares parent tool closures) and `RpcProcessRunner` (spawns a child Senpi process with JSON-RPC steer/abort/prompt). RPC internals under `src/runners/rpc/`. |16| Manager | `src/manager/` | `createTaskManager` wiring runners, concurrency, name registry, depth policy, execution-mode resolution, and transcript logging. |17| Lifecycle | `src/lifecycle/` | `createTaskLifecycle` - residency admission (`residency.ts`), TTL sweep (`ttl.ts`, skips records with a live resident handle so deletion cannot orphan an in-memory handle), crash reconcile (`reconcile.ts`), and shutdown teardown (`shutdown.ts`). |18| Completion | `src/completion/` | `createCompletionNotifier` + `routeCompletion` - the exactly-once wake/deliver/buffer/queue routing table (`completion/routing.ts`). |19| Steering | `src/steering/` | `createSteeringEngine` - send / interrupt / cancel against a live or resident child. |20| Team | `src/team/` | Named-team registry, normalize/validate, durable mailboxes with injection-driven delivery, lead poller, member self-polling extension, tasklist, shutdown handshake, and runtime (`team/runtime.ts`). |21| Tools | `src/tools/` | `task/` (single or `tasks:[...]` batch spawn), `control/` (`task_send`/`task_cancel`), `output/` (`task_output`), `team/` (the 6 lead-only team tools). |22| Agents | `src/agents/` | `loadAgents` + `mapOmoConfigAgents` - omo.json agent definitions to task-tool targets - plus the builtin curated agents (`agents/builtin/`) and `resolveAgent` agent-aware model/persona resolution. |23| Category | `src/category/` | `resolveCategory` + per-provider builtin category tables (anthropic/openai/google/kimi), including the `requiresModel` activation gate. |24| Adversarial | `src/__adversarial__/` | Seeded 200-iteration chaos bench asserting the four W1 invariants (`chaos-bench.test.ts`). |2526## PUBLIC API (`src/index.ts` barrel)2728### Task tools (4, names as registered)2930| Tool | Factory | File |31|------|---------|------|32| `task` | `createTaskTool` | `tools/task/tool.ts:9` (`TASK_TOOL_NAME`) |33| `task_send` | `createTaskSendTool` | `tools/control/send.ts` |34| `task_cancel` | `createTaskCancelTool` | `tools/control/cancel.ts:61` |35| `task_output` | `createTaskOutputTool` | `tools/output/output.ts` |3637`task` is spawn-only. It accepts either one `prompt` or a non-empty `tasks:[...]` batch; synchronous batches aggregate every child result, while background batches return item ids and queue positions. Steer, resident-session revival, team messaging, and shutdown approval traffic goes through `task_send`; child output and single-child status/transcript peeks go through `task_output`.3839### Team tools (6, lead-only)4041`buildLeadTeamTools(deps)` returns them in canonical order (`tools/team/index.ts`): `team_create`, `team_delete`, `task_create`, `task_get`, `task_list`, `task_update`. Child/member sessions never receive the lead family. Each process member loads the bundled member extension in-child and receives only team-scoped `task_send`; lead mail is steered into the resident member's running turn. It never receives lead lifecycle or tasklist tools.4243`packages/omo-opencode` is a separate build that still uses its prior task/team names; cross-edition parity is a deliberate follow-up outside this package.4445### Engine primitives4647### Category activation gating4849A builtin category may declare `requiresModel` (a bare model id) in its `BuiltinCategoryDefinition`. `resolveCategory` treats such a category as unavailable - `model_unavailable`, excluded from `availableCategories`, and never routed through its fallback chain - unless the gate model is present in the live senpi registry. ANY explicit `omo.json` `categories.<name>` entry bypasses the gate, even a description-only one, mirroring `hasExplicitUserConfig` in `packages/omo-opencode/src/tools/delegate-task/categories.ts`. Gateway-transformed registry ids (`vercel/openai/gpt-5.6-sol`) satisfy a gate on their last path segment. Two builtins are gated today: `architect` on `claude-fable-5` and `ultrabrain` on `gpt-5.6-sol`, each with a fallback chain trimmed to its own model family so the gate cannot be bypassed by a cross-family rung.5051The task tool description cannot consult the registry - it is built at tool registration, before the model registry is captured - so `listTaskCategories` keeps gated builtins listed and appends a ` (requires <model>)` annotation instead. A category carrying an explicit `omo.json` entry is listed without the annotation. Spawn-time `resolveCategory` remains the sole authoritative gate.5253`createTaskManager`, `createTaskLifecycle`, `createCompletionNotifier` / `routeCompletion` / `shouldNotifyStatus`, `createSteeringEngine`, `InProcessRunner`, `RpcProcessRunner`, `createTaskRecordStore` / `resolveStateDir`, `transitionTaskRecord` / `createTaskRecord`, `resolveCategory`, `loadAgents` / `mapOmoConfigAgents`, `resolveAgent` / `BUILTIN_AGENTS` / `BUILTIN_AGENT_DEFAULTS` / `CURATED_READONLY_AGENT_NAMES`, plus the team runtime (`createTeam`, `deleteTeam`, `sendTeamMessage`, `createLeadPoller`, `WaitRegistry`, `resolveMemberExtensionEntryPath`, `createTeamTask`, `requestShutdown`/`approveShutdown`/`rejectShutdown`, ...) and their typed errors (`SenpiTeamSpecError`, `SenpiTeamRuntimeError`, `SenpiShutdownError`, `RunnerError`, `TaskRecordCollisionError`).5455### Builtin curated agents5657`agents/builtin/` ships four read-only curated subagents - `explore`, `librarian`, `metis`, `momus` - as `BUILTIN_AGENTS` / `BUILTIN_AGENT_DEFAULTS`, each pinned to `executionMode: "in-process"` with a senpi-adapted persona prompt, a 9-name tool allowlist (`read`, `find`, `grep`, `ls`, `bash`, `lsp_diagnostics`, `lsp_goto_definition`, `lsp_find_references`, `lsp_symbols`), and a mirrored per-agent fallback chain in `agents/builtin/fallback-chains.ts` (hand-mirrored from `packages/model-core/src/agent-model-requirements.ts`, same convention as `category/fallback-chains.ts`; no model-core dependency). For curated in-process children, `runners/in-process/curated-readonly-bash.ts` replaces Senpi's general shell with a same-name structured broker that directly executes only validated read-only GitHub queries and HTTPS retrievals; direct edit/write and mutating LSP tools remain excluded. `CURATED_READONLY_AGENT_NAMES` feeds `team/member-validator.ts`, which rejects a curated name in a team member spec because process-mode spawns (mandatory for members) drop persona instructions and the tool allowlist. `resolveAgent(name, agents, registry, options?)` resolves one merged agent definition into the persona (`instructions`, `toolAllowlist`, `agentType`, `agentExecutionMode`, `allowedSubagents`, `maxDepth`) plus a model, trying `def.model`, then each `def.models` entry, then the agent fallback chain; `disable: true` resolves `not_found`, and an explicit `options.modelOverride` skips registry access entirely so active headless explicit-model spawns keep working. The omo-senpi engine ignores `execution_mode` overrides for these four names so the boundary cannot be routed through the process runner; user-defined agents remain configurable. A successful resolution records `resolved_model.source: "agent"` (added to `RESOLVED_MODEL_SOURCES` in `state/types.ts` and parsed by `store/record-parse.ts`), alongside `"category"` and `"explicit"`.5859### Plan-gated agents6061`agents/invocation-guard.ts` classifies `metis` and `momus` as the plan-gated tier (`AGENT_INVOCATION_CONDITIONS`, `PLAN_GATED_AGENT_NAMES`): plan-review specialists spawnable only when the USER explicitly requested the `ulw-plan` workflow in this session (`hasUserRequested` - raw user input; a model-initiated SKILL.md read never satisfies it), a `.omo/plans/*.md` plan artifact was touched in-session at any root including worktrees (`hasPlanArtifact`), and `start-work` was never invoked (`hasInvoked`, any channel). `evaluateInvocationGuard(agentName, SkillInvocationState)` is the pure verdict - forbidden skills are checked before missing requirements because a post-`start-work` denial is terminal, and denial messages deliberately avoid naming any mechanical unlock step. The session-scoped state arrives through the optional `TaskToolDeps.resolveSkillInvocations(sessionId)`; an unwired dep fails CLOSED (no state, no proof of a user request). The task tool consults the gate in `tools/task/invocation-gate.ts` before `manager.start` on both the single-spawn path (result status `denied`) and the batch path (the item fails as `plan_unresolved`/`invalid_target` with the gate message), and `buildTaskToolDescription` partitions gated names into their own `Plan-gated agents (...)` line instead of the flat `Available agents` list. omo-senpi supplies the live session state; any other host wiring this engine must provide its own resolver or accept the closed default.6263## TEAM DELIVERY MODEL6465Team messaging is injection-driven over durable mailboxes. A send writes a durable unread JSON file and returns; delivery steers the message into the recipient's running turn without queuing an editable follow-up. The current lead owns one `createLeadPoller` per team whose durable `leadSessionId` matches the current session. The adapter ticks owned lead pollers on `session_start` and every second, but suspends ticks during compaction, session switching, and shutdown. Member inboxes are never polled by the adapter: each process member loads `member-extension/`, which owns that member's poller and scoped tools inside the child process.6667Delivery is reservation-based: unread `<messageId>.json` becomes `.delivering-<messageId>.json`, then commits to `processed/<messageId>.json` only after the message is observed in the recipient session (the pre-injection `team_wait` claim path was removed). The processed file is the durable exactly-once ledger.6869Persistence of the delivered `peer_message` envelope in the lead's session JSONL is checked by `createSessionMarkerIndex` (`team/messaging/session-marker-index.ts`): a per-path incremental byte-offset index that reads only bytes appended since the last check, so the many `messageId` lookups per tick are O(1) instead of re-reading and re-parsing the whole file. It handles file truncation/rotation by rescanning from zero, and reads nothing when the file has not grown.7071Every `session_start` runs recovery in order: reattach durable process members, reclaim stale member and owned-lead reservations, retry failed completion notifications, then poll owned leads. Dead process members with a persisted session are respawned without replaying their original prompt and rebound with `switch_session`; set `task.reattach_on_reconcile: false` only to retain the old lost-task behavior.7273### Completion routing table (`completion/routing.ts`)7475`shouldNotifyStatus` fires only for externally-caused terminals `completed`/`error`/`lost` (`routing.ts:4`); parent-initiated cancel/interrupt return synchronously in the tool result and never push. `routeCompletion` maps parent state to an action: `idle` -> `wake` and `streaming` -> `deliver_streaming`, both delivered unconditionally (no setting may suppress, delay, or split them - the omo-senpi coordinator batches every notification ready in the same window into ONE injection steered into the running turn at the next tool-call boundary), and `compacting`/`session_switching`/`session_shutdown` -> `buffer` until the parent settles (`routing.ts:12`).7677## EXECUTION MODES7879- **in-process (default)**: `InProcessRunner` runs the child through the SAME parent tool closures (`filterSharedParentTools` + `mergeChildCustomTools`), so a child sees the parent's live custom tools minus the `task_*`/`team_*` family. Proven by the marker-tool test (`src/runners/in-process/marker-suppression.test.ts`).80- **process**: `RpcProcessRunner` spawns a child Senpi process; steering (`steer`/`abort`/`prompt`) crosses a JSON-RPC boundary (`src/runners/rpc/protocol-client.ts`), child transcripts land under `<stateDir>/children/<taskId>/sessions/<taskId>/`, and session-start reconciliation can respawn and `switch_session` to the newest persisted JSONL. Team members always use this mode so the member extension and durable inbox poller live inside the child.8182Mode is chosen by `resolveExecutionMode` from the omo.json `task.default_execution_mode` and per-agent `execution_mode` (`src/manager/execution-mode.ts`).8384## QA8586```sh87tsgo --noEmit -p packages/senpi-task/tsconfig.json88bun test packages/senpi-task89```9091- Co-located `*.test.ts` throughout use given/when/then. The seeded chaos bench (`src/__adversarial__/chaos-bench.test.ts`, 200 iterations, `SEED=<label>` to rerun a seed) asserts: (1) exactly-once notification per `(task_id, run_epoch)`, (2) terminal idempotence, (3) no concurrency slot leak, (4) no unhandled rejection.92- Standalone manual QA scripts write a disposable fixture tree and never touch repo state: `bun packages/senpi-task/scripts/manual-qa.ts <evidence-dir>` (store + transitions), plus `manual-category-qa.ts`, `manual-agents-qa.ts`, `manual-output-qa.ts`.93- Live end-to-end proof runs through the `omo-senpi` task component drivers, not this package alone. `task-e2e.mjs` proves single and `tasks:[...]` batch delegation; `team-e2e.mjs` proves injection-driven delivery, reservation reclaim, and kill-between-inject-and-commit restart deduplication. See [`packages/omo-senpi/AGENTS.md`](../omo-senpi/AGENTS.md).9495Parent: [`packages/AGENTS.md`](../AGENTS.md).96
Also in code-yeongyu/oh-my-openagent
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| code-yeongyu/oh-my-openagentpackages/omo-opencode/src/features/claude-code-agent-loader/AGENTS.md · 67k | AGENTS.md | archdeploymentagent-behaviour | 62/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/omo-opencode/src/features/claude-code-mcp-loader/AGENTS.md · 67k | AGENTS.md | lint-formatarchsecuritydeployment+1 | 66/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/omo-opencode/src/tools/background-task/AGENTS.md · 67k | AGENTS.md | arch | 70/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentscript/AGENTS.md · 67k | AGENTS.md | buildtestarchdeployment | 80/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/omo-opencode/src/features/claude-code-plugin-loader/AGENTS.md · 67k | AGENTS.md | archdeploymentagent-behaviour | 70/100 | 2 days ago | |
| code-yeongyu/oh-my-openagent.agents/AGENTS.md · 67k | AGENTS.md | stylearchtesting-strategydatabase | 64/100 | 2 days ago | |
| code-yeongyu/oh-my-openagent.opencode/AGENTS.md · 67k | AGENTS.md | stylearchdo-not | 71/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentAGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+11 | 84/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentassets/AGENTS.md · 67k | AGENTS.md | buildteststylearch | 88/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentbin/AGENTS.md · 67k | AGENTS.md | teststylearch | 64/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/AGENTS.md · 67k | AGENTS.md | buildtestlint-formatstyle+3 | 83/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/agents-md-core/AGENTS.md · 67k | AGENTS.md | archdependenciesapi | 48/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/boulder-state/AGENTS.md · 67k | AGENTS.md | archapi | 48/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/claude-code-compat-core/AGENTS.md · 67k | AGENTS.md | archagent-behaviour | 56/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/comment-checker-core/AGENTS.md · 67k | AGENTS.md | archdependenciesapi | 48/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/delegate-core/AGENTS.md · 67k | AGENTS.md | stylearchdependenciesapi | 60/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/git-bash-mcp/AGENTS.md · 67k | AGENTS.md | buildtestlint-formatarch+1 | 82/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/hashline-core/AGENTS.md · 67k | AGENTS.md | archtypesdependenciesapi | 48/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/lsp-core/AGENTS.md · 67k | AGENTS.md | archmonorepo | 43/100 | 2 days ago | |
| code-yeongyu/oh-my-openagentpackages/lsp-daemon/AGENTS.md · 67k | AGENTS.md | buildtestlint-formatarch+1 | 89/100 | today |
Diff against packages/omo-opencode/src/features/claude-code-agent-loader/AGENTS.md Diff against packages/omo-opencode/src/features/claude-code-mcp-loader/AGENTS.md Diff against packages/omo-opencode/src/tools/background-task/AGENTS.md Diff against script/AGENTS.md Diff against packages/omo-opencode/src/features/claude-code-plugin-loader/AGENTS.md Diff against .agents/AGENTS.md Diff against .opencode/AGENTS.md Diff against AGENTS.md Diff against assets/AGENTS.md Diff against bin/AGENTS.md Diff against packages/AGENTS.md Diff against packages/agents-md-core/AGENTS.md Diff against packages/boulder-state/AGENTS.md Diff against packages/claude-code-compat-core/AGENTS.md Diff against packages/comment-checker-core/AGENTS.md Diff against packages/delegate-core/AGENTS.md Diff against packages/git-bash-mcp/AGENTS.md Diff against packages/hashline-core/AGENTS.md Diff against packages/lsp-core/AGENTS.md Diff against packages/lsp-daemon/AGENTS.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | 3 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| SkeneTechnologies/skene-cookbookAGENTS.md · 51 | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 2 days ago | |
| trick77/agents-md-syncAGENTS.md · 2 | AGENTS.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago |
