task: deny was mistakenly added to config.permission alongside the
council tool denials. This globally denied the task delegation tool
for all agents, overriding per-agent task: allow for sisyphus, atlas,
etc. Only council tools should be globally denied.
Adds generic self-recursion guard in subagent-resolver using
getAgentConfigKey() to normalize display names and config keys.
Catches athena-junior spawning itself — the only subagent with
task() access not already covered by existing guards.
Remove dead agent.tools fields that only work during Zod parsing
(before plugin config hook runs). Council tool restriction now uses
config.permission deny globally + per-agent permission allow for
athena/athena-junior, which is the correct mechanism per OpenCode's
LLM.resolveTools() → PermissionNext.disabled() filtering path.
The v1 SDK client does not throw on 4xx/5xx — it resolves with an
{ error } object. navigateTuiToSession was treating any resolved promise
as success, silently misreporting failed navigation.
The v1 SDK Tui class has no selectSession method (only exists in v2).
Commit 4150e26d regressed this by replacing the working _client.post()
direct HTTP call with client.tui.selectSession() which silently failed.
Reverts to bypassing the SDK and POSTing to /tui/select-session directly,
which works regardless of SDK version.
Step 10 retry flow now instructs Athena to call prepare_council_prompt
again (since the original prompt file was moved into the archive by
council_finalize) and references Step 6 (background_wait) instead of
Step 5 (save prompt + launch).
Council members now skip TTL check in poller via isCouncilMemberAgent()
prefix match, covering both interactive (task tool) and bulk (athena_council)
launch paths. Users can cancel manually if needed.
Also fix promptFileMoved variable scoping: move declaration before try block
so finally block can access it. Dynamic timeout in error messages.
Add universal YAML front-matter header to all synthesis.md outputs (council,
question, date, members, session_ids, mode, intent, responded count).
AUDIT: structured finding format with required fields (severity, confidence,
members reported, evidence, impact, fix direction). New cross-check flow asks
user to verify minority/solo findings via a second council round before
proceeding to findings processing.
Also fix prompt-file cleanup: skip cleanupPromptFile when movePromptFile
already succeeded.
Interactive prompt: Athena now asks the user whether to retry failed members
or skip and synthesize with available results. Recommends based on success ratio.
Non-interactive prompt: always proceeds to synthesis with available results,
sets status to partial/failed accordingly.
Add optional ttl field to BackgroundTask/LaunchInput. Council launcher
passes COUNCIL_MEMBER_TTL_MS (2 hours) per member. Task poller uses
task.ttl ?? TASK_TTL_MS, replacing the blanket TTL_EXEMPT_AGENTS bypass
for council members with a bounded timeout.
Co-authored-by: Vacbo <2445>
Remove blind 30-min setTimeout cleanup from prepare_council_prompt. Prompt
files now stay until council_finalize archives them in a try/finally block.
Extract shared resolvePromptTempFilePath helper used by both movePromptFile
and the new cleanupPromptFile safety net.
Co-authored-by: Vacbo <2445>
Resolve symlinks via realpathSync before checking path policy, preventing
symlink-based escapes from .sisyphus/ sandbox. Walks up to nearest existing
ancestor for new-file writes. Adds 7 tests including real-filesystem symlink
scenarios.
Co-authored-by: Vacbo <2445>
Add 7 guardrails to stop Athena from acting as an implementation
orchestrator when receiving feature requests:
- Role + Tool Firewall: task() exclusively for council, todowrite banned
- Implementation-first pre-check: highest precedence in routing
- Expanded Category B: broader signals (build, make a feature, etc.)
- Strengthened Category F: vague features = ambiguous by default
- Handoff context integrity: no fabricated specs in switch_agent
- Routing read budget: 3-file cap for routing context
- New constraints: task() scope, todowrite ban, drift detection
Fixes bug where Athena created 9 todos and orchestrated implementation
via task() instead of routing to Prometheus/Sisyphus.
- Fix misleading background_wait instructions to show loop pattern with
remaining_task_ids (race semantics, not block-all)
- Fix wrong Gemini model string: gemini-3-pro-preview → gemini-3-pro
- Add getMainSessionID() fallback when input.sessionID is undefined
- Narrow .gitignore packages/ to only ignore built binaries
- Fix contradictory config doc: non_interactive_members 'all' → 'custom'
- Add athena-junior to keyword-detector exclusion check
- Add .trim() before .toUpperCase() in resolveCouncilIntent
- Update snapshots for model string change
Added missing documentation for:
- Council resilience settings (retry_on_fail, stuck_threshold, member_max_running)
- bulk_launch config for launch strategy
- Council tools (prepare_council_prompt, athena_council, council_finalize)
- Council archives (.sisyphus/athena/)
- Force-background and TTL exemption for athena-junior
- Council-member agent overrides and tool allowlist
- How to invoke athena-junior (task() and CLI)
- Updated agent list to include athena and council-member
- Added Athena subsections to table of contents
- Auto-generated schema.json update for bulk_launch field
The background_wait instruction was only shown when the caller passed
run_in_background=false (forced case). But callers passing true also need
the guidance since they were polling background_output instead. Now
athena-junior always gets background_wait instructions regardless of how
it was launched.
When athena-junior is force-backgrounded (caller passed run_in_background=false),
the response now instructs the caller to use background_wait instead of
background_output, preventing repeated polling loops.
Council sessions are long-running: athena-junior launches members then
waits for them via background_wait. Two changes prevent premature kills:
- Force run_in_background=true when subagent_type resolves to athena-junior,
avoiding the 10-minute sync poll timeout (MAX_POLL_TIME_MS)
- Exempt athena-junior from the 30-minute TASK_TTL_MS hard cutoff since
council members have their own independent TTLs
- Fix missing bulk_launch field in AthenaOverrideConfigSchema (type error)
New config: agents.athena.bulk_launch (boolean, default false)
- false: launch members one-by-one via task() for TUI inspectability
- true: launch all members at once via athena_council tool
Prompt uses {BULK_LAUNCH_STEP_5_2} placeholder injected at runtime
based on config value. Schema, caller, and tests updated.
The config.tools boolean false wasn't blocking plugin-registered tools.
Add prepare_council_prompt, council_finalize, athena_council to global
permission deny — same pattern as task: deny. Per-agent allow on
athena/athena-junior overrides the global deny.
Council tools (prepare_council_prompt, council_finalize, athena_council) are
globally disabled and only re-enabled for athena and athena-junior. Athena
was missing athena_council — now both agents have symmetric access.
Also fix parity test to include council_finalize and athena_council in
ATHENA_HANDLER_GRANTS for complete coverage.
Keep the council result JSON lean as a metadata envelope. Full synthesis
is already persisted at {archive_dir}/synthesis.md — duplicating it in
the JSON created noise that obscured structural fields like archive_dir.
Also instructs athena-junior to remind the caller to read the full
synthesis and member responses from the archive directory.
Athena primary should not be routable via switch_agent or appear in the
delegation table. Only athena-junior (subagent) remains delegatable via task().
- Remove athena from ALLOWED_AGENTS and DESCRIPTION in switch-agent tool
- Remove ATHENA_PROMPT_METADATA from agentMetadata, agent.ts, barrel export
- Update switch-agent test to assert athena rejection
- Remove ATHENA_PROMPT_METADATA test block
- Create src/tools/athena-council/ with council-launcher, tool factory, types
- Launch all council members in parallel via BackgroundManager with writeOutputToFile: true
- Wire tool into registry with backgroundManager + councilConfig dependencies
- Grant athena-junior athena_council permission, remove task permission
- Update non-interactive prompt to use athena_council instead of task tool
- Add explicit stop-after-result instruction to prevent post-output text
- 23 tests (14 tool + 9 launcher) using real temp files, no mock.module
Split the Athena council orchestrator into two distinct agents:
- athena: primary mode, interactive prompt with Question tool and switch_agent
- athena-junior: subagent mode, non-interactive prompt with structured JSON output
Key changes:
- Create athena-junior-agent.ts factory (mode=subagent, denies question+call_omo_agent)
- Revert athena agent.ts to primary mode (no env var switching)
- Make buildAthenaRuntimeGuidance mode-aware (strips action_paths for non-interactive)
- Add mode parameter to council_finalize tool
- Register athena-junior in builtin-agents, tool-config, model-requirements
- Replace "athena" with "athena-junior" in call_omo_agent ALLOWED_AGENTS
- Update all tests for new architecture
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add 4 new .replace() calls for NON_INTERACTIVE_MODE, NON_INTERACTIVE_MEMBERS,
NON_INTERACTIVE_MEMBER_LIST, and BACKGROUND_WAIT_TIMEOUT_MS. Pass athena
non-interactive config from agent-config-handler to createBuiltinAgents.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The general-agents.ts isPrimaryAgent check only considered mode=primary,
causing Athena (now mode=all) to lose uiSelectedModel resolution.
Also adds metadata tests for non-interactive triggers.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>