athena-claude-coder 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. athena_claude_coder-0.4.1/.gitignore +274 -0
  2. athena_claude_coder-0.4.1/PKG-INFO +308 -0
  3. athena_claude_coder-0.4.1/README.md +289 -0
  4. athena_claude_coder-0.4.1/pyproject.toml +59 -0
  5. athena_claude_coder-0.4.1/scripts/athena-claude +162 -0
  6. athena_claude_coder-0.4.1/src/athena_claude_coder/__init__.py +17 -0
  7. athena_claude_coder-0.4.1/src/athena_claude_coder/app.py +492 -0
  8. athena_claude_coder-0.4.1/src/athena_claude_coder/auth.py +45 -0
  9. athena_claude_coder-0.4.1/src/athena_claude_coder/main.py +113 -0
  10. athena_claude_coder-0.4.1/src/athena_claude_coder/permissions.py +222 -0
  11. athena_claude_coder-0.4.1/src/athena_claude_coder/redact.py +75 -0
  12. athena_claude_coder-0.4.1/src/athena_claude_coder/runner.py +1090 -0
  13. athena_claude_coder-0.4.1/src/athena_claude_coder/schema.py +62 -0
  14. athena_claude_coder-0.4.1/src/athena_claude_coder/settings.py +149 -0
  15. athena_claude_coder-0.4.1/src/athena_claude_coder/store.py +650 -0
  16. athena_claude_coder-0.4.1/src/athena_claude_coder/transcript.py +326 -0
  17. athena_claude_coder-0.4.1/src/athena_claude_coder/worktrees.py +151 -0
  18. athena_claude_coder-0.4.1/tests/__init__.py +1 -0
  19. athena_claude_coder-0.4.1/tests/conftest.py +306 -0
  20. athena_claude_coder-0.4.1/tests/test_guest_token.py +202 -0
  21. athena_claude_coder-0.4.1/tests/test_handoff.py +402 -0
  22. athena_claude_coder-0.4.1/tests/test_launcher.py +348 -0
  23. athena_claude_coder-0.4.1/tests/test_main.py +131 -0
  24. athena_claude_coder-0.4.1/tests/test_permissions.py +260 -0
  25. athena_claude_coder-0.4.1/tests/test_protocol_contract.py +540 -0
  26. athena_claude_coder-0.4.1/tests/test_runner_lifecycle.py +771 -0
  27. athena_claude_coder-0.4.1/tests/test_sdk_post_merge.py +64 -0
  28. athena_claude_coder-0.4.1/tests/test_store.py +343 -0
  29. athena_claude_coder-0.4.1/tests/test_transcript.py +531 -0
  30. athena_claude_coder-0.4.1/tests/test_worktrees.py +660 -0
@@ -0,0 +1,274 @@
1
+ # remove boilerplate repos
2
+ mock_api/
3
+ react-scripts_TS/
4
+ simple-nextjs_TS/
5
+ simple-react-srcipts_TS/
6
+ .DS_Store
7
+ .vercel/output/builds.json
8
+ .vercel
9
+ c2poutput
10
+ meeting_recordings/
11
+ node_modules
12
+
13
+ # Ignore Terraform files
14
+ .terraform/
15
+ *.tfstate
16
+ *.tfstate.*
17
+ backend/notebooks/gmailoauth/token.json
18
+ .next*
19
+ .vscode*
20
+ !.vscode/extensions.json
21
+
22
+
23
+ # papyrus
24
+ papyrus/**/dist/**/*
25
+ __pycache__
26
+ **/.ipynb_checkpoints/**/*
27
+ papyrus/jupyter-server-assistant-api/jupyter_server_assistant_api/static/
28
+
29
+ agora/download.pdf
30
+
31
+ .gait/custom-merge-driver.sh
32
+
33
+ .pre-commit-config.yaml
34
+
35
+ # Environment files
36
+ .env.production
37
+ .aider*
38
+ .env
39
+ # Hades env files are safe to commit (no secrets — just URLs and mode flags)
40
+ !hades/.env
41
+ !hades/.env.production
42
+ !hades/.env.example
43
+ commit-review.zsh
44
+
45
+ # CI logs output
46
+ /scripts/logs/
47
+
48
+ converter-olympus/dist
49
+ *.bun-build
50
+
51
+
52
+ /agora/scripts/yjs/
53
+ agora/converter-olympus/src/doc/tiptap.json
54
+ agora/converter-olympus/src/doc/markdown.html
55
+ agora/tmp/*
56
+ olympus/.eslint_fixed_files
57
+ olympus/.eslint_processing_files
58
+ olympus/.eslint_files_with_issues
59
+ olympus/.eslint_lock
60
+ precommit-prettier.log
61
+ precommit-biome.log
62
+
63
+ # Playwright
64
+ integration-tests/test-results/
65
+ integration-tests/playwright-report/
66
+ integration-tests/blob-report/
67
+ integration-tests/playwright/.cache/
68
+ olympus/test-results/
69
+ olympus/playwright-report/
70
+ olympus/playwright/.auth/
71
+ olympus/playwright/.cache/
72
+ agora/scripts/.migration_config.json
73
+ agora/scripts/.checkpoint_analysis_config.json
74
+ agora/scripts/.thread_cleanup_config.json
75
+ agora/scripts/.windmill_update_config.json
76
+ agora/ryvn/tmpenv
77
+ agora/tmpenv
78
+
79
+
80
+ # Last chosen Doppler config (script configuration)
81
+ .last_doppler_config
82
+
83
+ # Last chosen Infisical environment (script configuration)
84
+ .last_infisical_env
85
+
86
+ # Setup script hash files
87
+ .last_*_hash
88
+
89
+ # Setup completion markers
90
+ .setup_complete
91
+
92
+ agora/scripts/.interactive_runner_last
93
+ agora/scripts/.aop_backfill_config.json
94
+ agora/scripts/.workflow_runner_last.json
95
+
96
+ # Act workflow runner secrets
97
+ agora/scripts/.act.secrets
98
+
99
+ # Temporary reference repo for comments UI update
100
+ next-comments-template/
101
+ agora/test-results/
102
+
103
+
104
+ # SWE directories
105
+ swe/**/dist
106
+ swe/**/.turbo
107
+ # Claude Code local commands (user-specific, generated)
108
+ .claude/commands/local/
109
+ .branch-prefix
110
+
111
+ # Plans generated by models (untracked plans stay ignored; force-add specific files as needed)
112
+ docs/plans/*
113
+ # Customer-SSO computer-asset viewers (#31218) — design investigation & recommended architecture — deliberately committed
114
+ !docs/plans/2026-08-26-customer-sso-computer-asset-viewers-design.md
115
+ # Computer Time Travel v1 (snapshots + rollback) approved implementation plan — deliberately committed
116
+ !docs/plans/2026-08-27-computer-time-travel-snapshots.md
117
+ !docs/plans/2026-09-02-talos-shared-store-stability-thesis.md
118
+ !docs/plans/2026-08-31-microsandbox-boundary-secrets-design.md
119
+ !docs/plans/2026-09-01-talos-v2-golive-readiness-audit.md
120
+ # Computer SSH: identity-based access + `athena ssh` CLI + wake-on-connect — approved direction, deliberately committed
121
+ !docs/plans/2026-09-02-computer-ssh-identity-and-cli.md
122
+ # CLI browser login (RFC 8628 device authorization) — wire contract + security record, deliberately committed
123
+ !docs/plans/2026-09-02-cli-browser-login.md
124
+ !docs/plans/2026-08-31-office-addin-voice-ws-relay-spike.md
125
+ !docs/plans/2026-08-31-drain-auto-continue-superseded-gate-fix.md
126
+ # Admin dashboard consolidation audit — the stay/move/remove record behind the tab removals — deliberately committed
127
+ !docs/plans/2026-09-01-admin-dashboard-consolidation-audit.md
128
+ !docs/plans/2026-09-02-athena-agent-next-batch.md
129
+ !docs/plans/2026-09-02-claude-code-coding-worker-proposal.md
130
+ !docs/plans/2026-09-02-claude-code-coding-worker-implementation-plan.md
131
+ # Customer-SSO viewers (#31218) — post-implementation review & revised plan — deliberately committed
132
+ !docs/plans/2026-08-26-customer-sso-viewers-revised-plan.md
133
+ # Talos v2 hardening-audit roadmap (2026-08-29) — deliberately committed
134
+ !docs/plans/2026-08-29-talos-v2-hardening-roadmap.md
135
+ # Session Sandbox Toolkit (grants + placements) approved implementation plan — deliberately committed
136
+ !docs/plans/2026-08-28-session-sandbox-toolkit-grants-placements.md
137
+ # Talos production stability findings and overhaul plan — deliberately committed
138
+ !docs/plans/2026-08-25-canonical-source-identity-wave1.md
139
+ # BYO environments (Devin-style) + seamless GitHub connect program plan — deliberately committed
140
+ !docs/plans/2026-08-25-byo-environments-and-seamless-github.md
141
+ !docs/plans/2026-07-29-talos-production-stability-overhaul.md
142
+ # Computer warm adoption on a shared JuiceFS store (~1-2s spin-up) — deliberately committed
143
+ !docs/plans/2026-08-20-computer-shared-juicefs-warm-adoption.md
144
+ # Talos M2 completion plan, referenced from issue #28028 — deliberately committed
145
+ !docs/plans/2026-07-30-talos-m2-completion.md
146
+ !docs/plans/2026-08-04-agent-service-identity-and-vaults.md
147
+ # Prometheus catalog provider plan — deliberately committed
148
+ !docs/plans/2026-07-30-prometheus-catalog-asset.md
149
+ # SQL-in-notebooks (catalog-connected SQL cells) plan — deliberately committed
150
+ !docs/plans/2026-07-31-sql-in-notebooks.md
151
+ # Collab-agent orchestration layer (tasks/triggers/monitors/channels) plan — deliberately committed
152
+ !docs/plans/2026-08-01-collab-agent-orchestration-layer.md
153
+ # Environments-on-Talos program plan (environment primitive, phases 1-6) — deliberately committed
154
+ !docs/plans/2026-08-04-environments-on-talos-program-plan.md
155
+ # Environments program shipped-vs-remaining status record — deliberately committed
156
+ !docs/plans/2026-08-04-environments-program-status.md
157
+ # In-house Paragon-parity program plan (direct connectors) — deliberately committed
158
+ !docs/plans/2026-08-13-inhouse-paragon-parity-program.md
159
+ # 2026-08-19 refactoring audit backlog (issue #30277) — deliberately committed
160
+ !docs/plans/2026-08-19-refactoring-audit.md
161
+ # rnc audit artifacts — deliberately committed, kept genuinely un-ignored (not just force-tracked)
162
+ !docs/plans/rnc-phase0-harness/
163
+ !docs/plans/rnc-phase0-harness/**
164
+ !docs/plans/2026-06-12-rnc-ops-inventory.md
165
+ !docs/plans/2026-06-12-rnc-audit-remediation-plan.md
166
+ !docs/plans/2026-06-13-rnc-ws7-sharding-reconciliation.md
167
+ !docs/plans/2026-06-13-rnc-ws7-ytablestore-design.md
168
+ # Twilio SMS collab-agent channel design doc — deliberately committed
169
+ !docs/plans/2026-06-13-twilio-sms-collab-agent-channel.md
170
+ # Collab-agent calendar-invite attendance (Recall v1.11) design doc — deliberately committed
171
+ !docs/plans/2026-06-15-collab-agent-calendar-invite-attendance.md
172
+ # Collab-agent calendar-invite Phase 0 spike artifacts — deliberately committed
173
+ !docs/plans/calendar-invite-phase0/
174
+ !docs/plans/calendar-invite-phase0/**
175
+ # Tool Asset (Windmill runtime) design doc — deliberately committed
176
+ !docs/plans/2026-06-16-tool-asset-windmill-runtime.md
177
+ # Tool Asset (Custom Tool UI) design doc — deliberately committed
178
+ !docs/plans/2026-06-16-tool-asset-custom-ui.md
179
+ # Treasury credential-delegation design proposal — deliberately committed
180
+ !docs/plans/2026-06-22-treasury-credential-delegation-proposal.md
181
+ # OpenFGA rollout-readiness punch-list — deliberately committed (referenced by the guide)
182
+ !docs/plans/2026-06-25-openfga-rollout-readiness-punchlist.md
183
+ # OpenFGA DB-vs-store reconciliation/GC job design — deliberately committed
184
+ !docs/plans/2026-06-25-fga-db-vs-store-reconciliation-design.md
185
+ # Agora/Olympus preview pipeline build-time audit — deliberately committed
186
+ !docs/plans/2026-07-02-agora-olympus-preview-build-time-audit.md
187
+ # Katten destructive-AOP smoke-testing design doc — deliberately committed
188
+ !docs/plans/2026-07-07-katten-destructive-aop-smoke-testing.md
189
+ # Postgres connection & operation audit (2026-07-10) — deliberately committed
190
+ !docs/plans/2026-07-10-db-connection-audit.md
191
+ # Deep-agent runtime parity investigation findings (2026-07-21) — deliberately committed
192
+ !docs/plans/2026-07-21-deep-agent-runtime-parity-findings.md
193
+ # AOP orchestration refactor findings & plan (2026-07-24) — deliberately committed
194
+ !docs/plans/2026-07-24-aop-orchestration-refactor.md
195
+ # Mobile HITL approval push design (2026-07-26) — deliberately committed
196
+ !docs/plans/2026-07-26-mobile-hitl-approval-push.md
197
+
198
+ # Talos production stability milestones are long-lived implementation records.
199
+ !docs/plans/2026-07-30-talos-m4-rollout-readiness.md
200
+ !docs/plans/2026-07-30-talos-m3-completion-plan.md
201
+
202
+ !docs/plans/2026-08-01-talos-drain-barrier.md
203
+
204
+ !docs/plans/2026-08-21-event-loop-stall-remediation.md
205
+
206
+ !docs/plans/2026-08-24-sandbox-stack-audit-remediation.md
207
+ # Agent-sandbox strategy execution plan (companion to talos/docs/AGENT_SANDBOX_STRATEGY.md) — deliberately committed
208
+ !docs/plans/2026-08-25-agent-sandbox-strategy-execution.md
209
+ # AWS cost optimization private plaintext. Commit encrypted bundles only.
210
+ aws-cost-optimization/private/
211
+ aws-cost-optimization/shared/context.md
212
+ aws-cost-optimization/keys/*.pem
213
+ aws-cost-optimization/keys/*.pem*
214
+ !aws-cost-optimization/keys/aws-cost-public.pem
215
+ aws-cost-optimization/*.decrypted.md
216
+ aws-cost-optimization/.decrypt-private.*
217
+ aws-cost-optimization/.env
218
+ aws-cost-optimization/.env.*
219
+ !aws-cost-optimization/.env.example
220
+
221
+ # Next.js generated files
222
+ olympus/next-env.d.ts
223
+ agora/.pytest_cache
224
+ olympus/.turbo
225
+ .turbo
226
+
227
+ # SDK build output
228
+ packages/react/dist
229
+ packages/references/dist
230
+ packages/chat-sdk/dist
231
+ athenaintel-admin/**/dist
232
+
233
+ # Root bun workspace — only the root bun.lock is canonical. Reject any
234
+ # reintroduction of per-package lockfiles from other package managers.
235
+ pnpm-lock.yaml
236
+ pnpm-workspace.yaml
237
+ package-lock.json
238
+ yarn.lock
239
+ # desktop/ and hades/ are intentionally NOT part of the bun workspace —
240
+ # electron-builder's "install production dependencies" step shells out to
241
+ # $npm_execpath, which under bun resolves to the bun native binary and node
242
+ # then chokes parsing it as JS. Both projects keep their original lockfiles:
243
+ # desktop → npm (package-lock.json), hades → pnpm (pnpm-lock.yaml).
244
+ !desktop/package-lock.json
245
+ !hades/pnpm-lock.yaml
246
+ # Nested bun.lock files (root bun.lock wins)
247
+ agora/converter-olympus/bun.lock
248
+ packages/*/bun.lock
249
+ pptx-studio/**/bun.lock
250
+ olympus/bun.lock
251
+ keryx/bun.lock
252
+
253
+ # TypeScript build info
254
+ *.tsbuildinfo
255
+
256
+ # Deep Agents CLI (langchain-ai/deepagents)
257
+ .deepagents/
258
+
259
+ # Local git worktrees created by agent workflows
260
+ .worktrees/
261
+ .cmux/
262
+
263
+ # Chart dependencies are rebuilt by the release workflow before packaging.
264
+ helm/charts/athenaintel-sandbox/charts/*.tgz
265
+ helm/charts/athenaintel-orpheus/charts/
266
+
267
+ # Agent workflow scratch artifacts (never commit)
268
+ conversation_history/
269
+ /tmp/
270
+ !docs/plans/2026-08-26-computer-fork-and-vm-download.md
271
+ !docs/plans/2026-08-26-computer-use-on-computer-assets.md
272
+ # Mobile release pipeline repair (EAS quota) + OTA updates and mobile backlog — deliberately committed
273
+ !docs/plans/2026-08-27-mobile-app-pipeline-and-ota.md
274
+ !docs/plans/2026-09-02-warm-rnc-engine-service-design.md
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.5
2
+ Name: athena-claude-coder
3
+ Version: 0.4.1
4
+ Summary: In-guest Agent Protocol server that drives the Claude Agent SDK for Athena coding tasks
5
+ License: Proprietary
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: aiosqlite<1,>=0.20
8
+ Requires-Dist: claude-agent-sdk==0.2.151
9
+ Requires-Dist: fastapi<1,>=0.115
10
+ Requires-Dist: pydantic<3,>=2.7
11
+ Requires-Dist: uvicorn<1,>=0.30
12
+ Provides-Extra: dev
13
+ Requires-Dist: httpx>=0.27; extra == 'dev'
14
+ Requires-Dist: langgraph-sdk==0.4.2; extra == 'dev'
15
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
16
+ Requires-Dist: pytest>=8; extra == 'dev'
17
+ Requires-Dist: ruff>=0.6; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # athena-claude-coder
21
+
22
+ The in-guest **Agent Protocol** server that drives the **Claude Agent SDK** for
23
+ Athena coding tasks ([ADR 0006](../../docs/decisions/0006-claude-code-coding-worker.md)).
24
+
25
+ The Athena deep agent delegates repository work to a `claude-coder` async
26
+ subagent (`deepagents` `AsyncSubAgent`). Its calls travel through the agora
27
+ coding-worker proxy (`/api/coding-worker/{asset_id}/…`), which authorizes every
28
+ request, wakes the computer, ensures this server is running, mints a router
29
+ grant, and forwards to loopback port `46100` inside the tenant's Talos v2
30
+ computer. Here, one `ClaudeSDKClient` per thread works in the checkout at
31
+ `/workspace/template` and answers with a compact structured result.
32
+
33
+ | Route | Who calls it | What it does |
34
+ |---|---|---|
35
+ | `GET /ok` | the ensure command, the proxy | **unauthenticated** liveness `{ok, version}`; adds `busy`, `active_thread_id`, `owner`, `owner_since`, `owner_thread_id` only when the guest token header matches (never 401) |
36
+ | `POST /threads` | `threads.create` | create a task thread (the proxy supplies the id) |
37
+ | `GET /threads/{thread_id}` | `threads.get` | the `Thread` shape; `values.messages[-1].content` is the result JSON |
38
+ | `GET /threads/{thread_id}/state` | `threads.get_state` | `{values, next, metadata}` (parity) |
39
+ | `POST /threads/{thread_id}/runs` | `runs.create` | start a Claude turn; `multitask_strategy=interrupt` supersedes a live one |
40
+ | `GET /threads/{thread_id}/runs/{run_id}` | `runs.get` | the `Run` shape; `error` carries the reason on `error`/`interrupted` |
41
+ | `POST /threads/{thread_id}/runs/{run_id}/cancel` | `runs.cancel` | interrupt + bounded drain → 204; idempotent |
42
+ | `PUT /internal/credential` | the proxy | rotate the task owner key on disk → 204 |
43
+ | `POST /internal/handoff/request` | the `athena-claude` launcher | interrupt + drain the live run, lock the session for a human → `{thread_id, claude_session_id, cwd, owner}` |
44
+ | `POST /internal/handoff/return` | the launcher on exit | release the lock, record the session id the human ended on → `{thread_id, owner}` |
45
+ | `GET /threads/{thread_id}/transcript?after&limit` | the proxy (task card) | Claude Code's own session transcript for the thread, projected and redacted → `{thread_id, claude_session_id, total, entries}` |
46
+
47
+ Nothing else exists: no LangGraph in the guest, no streaming endpoints, no
48
+ OpenAPI docs.
49
+
50
+ ## Install
51
+
52
+ From a checkout — what the environment bake does today:
53
+
54
+ ```bash
55
+ uv tool install --python 3.12 /workspace/template/python-sdk/athena-claude-coder
56
+ athena-claude-coder --version
57
+ athena-claude-coder serve
58
+ install -m 0755 /workspace/template/python-sdk/athena-claude-coder/scripts/athena-claude /opt/athena/bin/athena-claude
59
+ ```
60
+
61
+ From PyPI — **only after the first human-triggered publish**. Nothing publishes
62
+ automatically: a human dispatches
63
+ `.github/workflows/publish-athena-claude-coder.yml` from `staging` with
64
+ `confirm=publish` and the `version` that equals `__version__` in
65
+ `src/athena_claude_coder/__init__.py` (the upload job runs in the `pypi` GitHub
66
+ environment, which holds the token and its approval/branch rules); until that
67
+ has happened for a version, `athena-claude-coder==<version>` does not resolve.
68
+
69
+ ```bash
70
+ uv tool install --python 3.12 athena-claude-coder==<version>
71
+ athena-claude-coder --version # must print that version; the proxy refuses guests below its minimum
72
+ ```
73
+
74
+ The wheel carries the server only. The `athena-claude` human launcher
75
+ (`scripts/athena-claude`) is not in it — install it from the checkout (the line
76
+ above) or from the bake runbook's inline copy.
77
+
78
+ `claude-agent-sdk==0.2.151` is pinned exactly and bundles the Claude Code CLI.
79
+
80
+ ## Configuration
81
+
82
+ Everything comes from the environment; the proxy's ensure command exports it
83
+ before `pm2` starts the server.
84
+
85
+ | Variable | Default | Meaning |
86
+ |---|---|---|
87
+ | `ATHENA_CODING_WORKER_PORT` | `46100` | loopback port (never public; the router grant targets it) |
88
+ | `ATHENA_CODING_WORKER_STATE_DIR` | `/workspace/.claude-state` | SQLite, run logs, credential, `settings.json`; also `CLAUDE_CONFIG_DIR` |
89
+ | `ATHENA_CODING_WORKER_REPO_DIR` | `/workspace/template` | Claude's `cwd` |
90
+ | `ATHENA_CODING_WORKER_DRAIN_SECONDS` | `30` | how long an interrupt waits for the turn's terminal message before killing the task |
91
+ | `ATHENA_CODING_WORKER_MAX_RUN_HOURS` | `6` | runs older than this are interrupted by the sweep |
92
+ | `ATHENA_CODING_WORKER_TOKEN` | — | shared secret; **required** for every route except `GET /ok` |
93
+
94
+ ## The gate
95
+
96
+ This server is not the security boundary — the microVM, the agora proxy
97
+ (per-request `get_asset_with_permission_check` + ABAC EDIT) and the gateway
98
+ budget are. Two things here are still load-bearing:
99
+
100
+ - **Guest token.** Every route except `GET /ok` requires
101
+ `X-Athena-Coding-Worker-Token` to equal `ATHENA_CODING_WORKER_TOKEN`
102
+ (constant-time compare) → otherwise `401`. If the variable is unset the
103
+ server fails closed: those routes answer `503 {"detail": "guest token not
104
+ configured"}`. `GET /ok` never 401s, but without a matching token it says
105
+ only `{ok, version}`; the proxy's ensure command proves the running server
106
+ adopted the current token with `GET
107
+ /threads/00000000-0000-4000-8000-000000000000` → `404` (wrong token `401`,
108
+ unset `503`).
109
+ - **`can_use_tool`.** Fail-closed defence in depth for Claude's tool calls:
110
+ read-only tools pass, writes are confined to the checkout, the state dir and
111
+ `/tmp`, destructive shell verbs are never allowed, and `git push`,
112
+ `gh pr …`, publishing and force operations need an explicit
113
+ `AUTHORIZED: <action>` line in the task text. The credential files under the
114
+ state dir are off-limits to every tool (and the search tools may not be
115
+ pointed at the state dir root); shell references to them or to the
116
+ `athena-claude-key` helper are refused.
117
+ - **One live task per computer.** Every task shares one checkout and one
118
+ credential file, so the envelope's `max_concurrent_tasks` is clamped to 1
119
+ until worktree-per-task lands (plan P2.4); a second thread gets `409
120
+ computer busy`. The slot is reserved before anything is written, so a
121
+ create that loses a race leaves no transcript entry, run row or credential.
122
+
123
+ ## Credentials
124
+
125
+ The run's `config.configurable.athena` envelope — injected by the proxy, never
126
+ trusted from anywhere else — carries the task owner key. The server writes it
127
+ to `<state_dir>/credential` (mode `0600`, atomic temp+rename, raw key only) and
128
+ `<state_dir>/credential.json` (`expires_at`, `budget_user_id`,
129
+ `anthropic_base_url`). Claude reads the key through the `apiKeyHelper`
130
+ (`/opt/athena/bin/athena-claude-key`, installed by the bake) named in the
131
+ `settings.json` that `serve` writes; the server process never passes it as an
132
+ environment variable.
133
+
134
+ `serve` also **removes** `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
135
+ `CLAUDE_CODE_OAUTH_TOKEN` and `CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR` from its
136
+ own environment before any client is built: `ClaudeAgentOptions.env` is merged
137
+ *over* `os.environ` and cannot unset a key, and the computer's own gateway key
138
+ is in the inherited environment.
139
+
140
+ ## Runs, results, durability
141
+
142
+ - **One consumer.** Only the run task iterates `client.receive_response()`.
143
+ Interrupts (cancel, `multitask_strategy=interrupt`, the sweep, shutdown) set a
144
+ flag and call `client.interrupt()`; the same loop drains the interrupted
145
+ turn's `ResultMessage` and records `interrupted`. The drain is bounded by
146
+ `ATHENA_CODING_WORKER_DRAIN_SECONDS`; past it the task is cancelled and the
147
+ SDK closes the subprocess.
148
+ - **Result JSON.** The last AI message is the compact result
149
+ (`status ∈ completed|blocked|failed`, `summary`, `branch`, `commit_sha`,
150
+ `changed_files`, `tests`, `artifacts`, `blockers`, `questions`, plus
151
+ `claude_session_id`, `cost_usd`, `num_turns`, `terminal_reason`, `run_id`).
152
+ Budget exhaustion is `status: blocked` with `blockers: ["budget exhausted"]`.
153
+ A run is `success` whenever Claude produced a terminal result — including
154
+ error results — because the parent only reads the message on `success`; a
155
+ run is `error` only when the SDK or process failed.
156
+ - **Same thread = same Claude session.** The session id from the `init` message
157
+ is persisted immediately; every later run on the thread resumes it.
158
+ - **Suspend-safe.** State is SQLite plus files on the persisted tree. At boot
159
+ every `pending`/`running` row becomes `interrupted` (`error: restart`) with
160
+ its session id intact; the sweep interrupts runs older than
161
+ `ATHENA_CODING_WORKER_MAX_RUN_HOURS`.
162
+
163
+ ## Human handoff (P2.1)
164
+
165
+ A human in the computer terminal (Olympus terminal tab or ssh) can take a
166
+ task's Claude session over and give it back, on the **same** session: both
167
+ sides share `CLAUDE_CONFIG_DIR` and the checkout, so the next SDK run resumes
168
+ exactly where the human stopped.
169
+
170
+ | State (`sessions.owner`) | Entered by | What the SDK may do |
171
+ |---|---|---|
172
+ | `sdk` | a run starting | — (it is the run) |
173
+ | `human` | `POST /internal/handoff/request`: the live run is interrupted with reason `handoff`, drained through the single stream consumer, and recorded as a **`success`** whose last AI message is `{"status": "blocked", "summary": "Handed off to a human in the computer terminal", "blockers": ["human_active"], "claude_session_id", "run_id"}` — `success` so `check_async_task` reads it | `runs.create`/`reserve` → `409 human holds the session`, nothing written |
174
+ | `none` | `POST /internal/handoff/return` (records the session id the human ended on), a run finishing, or a restart clearing a dead `sdk` holder | start a run; it passes `resume=<claude_session_id>` |
175
+
176
+ Both routes are idempotent and need the guest token. `owner_since` is the
177
+ moment the lock was taken (`null` for `none`) and doubles as the lock's
178
+ **lease**: the request returns it (with `acquired: true` the first time,
179
+ `false` while a human already holds the session), the return must present
180
+ it, and a return with a stale lease — or one arriving while the SDK owns the
181
+ thread — is a no-op. A human lock also occupies the computer's one task slot,
182
+ so other threads are refused with `409 computer busy` while it is held. The
183
+ request carries the launcher's `launcher_pid`; a lock whose launcher process
184
+ is gone (closed terminal, suspended computer) is released at startup, before
185
+ every reservation and by the sweep. `takeover: true` re-mints the lease for a
186
+ second terminal; the earlier launcher's return then does nothing. The
187
+ authenticated `GET /ok` reports `owner` and `owner_since` for the live thread,
188
+ else the human-held or most recent session, and names that thread in
189
+ `owner_thread_id` (`null` when no session exists) — once a handoff has
190
+ drained the run and `active_thread_id` is gone, this is what ties a human
191
+ lock to its task.
192
+
193
+ `scripts/athena-claude` is the launcher the bake installs at
194
+ `/opt/athena/bin/athena-claude`: it reads the guest token from the running
195
+ server's pm2 environment (`PM2_HOME=/workspace/.pm2-athena-coder pm2 jlist`,
196
+ never printed), requests the handoff, runs `claude --resume <session>` (or a
197
+ fresh `--session-id` it chooses) with the SDK's exact environment and cwd, and
198
+ on exit — any exit code, via `trap`, retried while the worker is unreachable —
199
+ posts the return with the lease and the session id it ended on. It refuses
200
+ to start a second writer when another terminal holds the session
201
+ (`ATHENA_CLAUDE_TAKEOVER=1` takes it over). `ATHENA_CODING_WORKER_THREAD_ID`
202
+ picks a thread explicitly; the default is the live one, else the most recent
203
+ session.
204
+
205
+ ## Concurrency
206
+
207
+ The run envelope's `max_concurrent_tasks` (the workspace policy) sets the
208
+ number of **task slots**, capped at `HARD_MAX_CONCURRENT_TASKS = 4`. With one
209
+ slot (the default) a task runs in the checkout itself (`ATHENA_CODING_WORKER_REPO_DIR`),
210
+ exactly as before. With more, every task thread gets its own **git worktree**:
211
+
212
+ - `git worktree add /workspace/worktrees/<thread12> -b athena/<thread12>` from the
213
+ checkout's current `HEAD` on the thread's first run (`thread12` = first 12
214
+ characters of the thread id); the path is persisted as the thread's `cwd`, so
215
+ every later run — and a human handoff — lands in the same worktree. An
216
+ existing worktree or branch (crash between create and persist, or a
217
+ garbage-collected worktree) is adopted / re-attached, never recreated.
218
+ - A thread occupies one slot while it has a live run, a reservation or a human
219
+ holder; the (N+1)th concurrent thread is refused with `409 computer busy`, a
220
+ follow-up run on a thread that holds a slot reuses it. Slot indices are
221
+ stable while a thread holds its slot; `GET /ok` reports `slots: {used, total}`,
222
+ `active_thread_ids` (oldest first) and keeps `active_thread_id` (the oldest
223
+ live one); `busy` means every slot is taken.
224
+ - Each task's system prompt names its dev-server port, `46200 + slot index`,
225
+ and warns that other tasks may be using the neighbouring ports.
226
+ - Each task reads **its own credential**: `runs.create` writes the envelope's
227
+ owner key to `<state_dir>/credentials/<thread_id>` as well as to the shared
228
+ `credential` file, and the run's `--settings` file
229
+ (`settings-<thread_id>.json`) names an `apiKeyHelper` that `cat`s that copy,
230
+ so concurrent tasks never bill each other's key. The shared file and the
231
+ baked helper stay for the human launcher; `PUT /internal/credential` may
232
+ carry `thread_id` to rotate a task's copy too.
233
+ - A human handoff takes a slot exactly like a run (refused with `409 computer
234
+ busy` beyond the policy) and a never-run thread handed to a human gets its
235
+ own worktree under a multi-slot policy.
236
+ - Threads that already work in the checkout (first run under one slot, or any
237
+ 0.2.0 row) keep it for transcript continuity; when the policy grows, at most
238
+ one such thread may be active at a time — a second is refused with `409
239
+ computer busy` rather than allowed to share the tree. Two thread ids that
240
+ share their first 12 characters get distinct worktrees (the second takes a
241
+ longer name).
242
+ - **GC** runs on the sweep cadence and removes a worktree only when its thread
243
+ is terminal (no live run, no reservation, owner `none`), the worktree is clean
244
+ (`git status --porcelain` empty *and* no commit ahead of the checkout's
245
+ `HEAD`) and its session has been idle for more than 24 h; eligibility is
246
+ re-checked under the runner lock right before the removal, so a thread
247
+ claimed meanwhile keeps its worktree. A dirty or ahead worktree is kept and
248
+ logged once. The `athena/<thread12>` branch is never deleted — it is the
249
+ work product — and the checkout itself is never touched.
250
+
251
+ ## Transcript route
252
+
253
+ `GET /threads/{thread_id}/transcript?after=<int>&limit=<int>` (guest token
254
+ required) reads Claude Code's own session transcript for the thread — the
255
+ JSONL under `CLAUDE_CONFIG_DIR/projects/<project-slug>/<claude_session_id>.jsonl`,
256
+ where the project slug follows the thread's cwd (its worktree under a
257
+ multi-slot policy). Agora reads a task's turns *through* this route instead of
258
+ the guest pushing them into the Athena session: the session bridge has no
259
+ credential a guest could hold. The read is never taken under the runner lock
260
+ and runs in a worker thread.
261
+
262
+ - `404 thread not found` for an unknown thread; `404 no transcript for this
263
+ thread yet` while the thread has no `claude_session_id` or the file does not
264
+ exist yet.
265
+ - One entry per content block of every user/assistant message:
266
+ `{seq, role: "user"|"assistant", kind: "text"|"tool_use"|"tool_result"|"thinking",
267
+ text, tool_name?, ts?}`. `seq` is a stable 0-based index over the whole file,
268
+ `text` is clipped to 4000 characters (a `tool_use` renders compactly — the
269
+ Bash command, the Read/Edit/Write path, the Grep pattern — and a
270
+ `tool_result` carries the result text and the name of the tool it answers),
271
+ `ts` is the entry timestamp. Summary, system and meta lines are skipped.
272
+ - The projection follows the **live conversation** the way the pinned SDK's
273
+ session reader finds it: the most recent turn no later record descends
274
+ from, and its `parentUuid` ancestors. Turns of an abandoned branch (a
275
+ rewind in the terminal during a handoff) and subagent `isSidechain` records
276
+ are left out. Unlike the SDK, a `compact_boundary` is crossed through its
277
+ `logicalParentUuid`, so the turns before an auto-compaction stay in the
278
+ card and the `isCompactSummary` message appears in their flow — following
279
+ the SDK here would make `total` collapse at every compaction. `seq` is
280
+ therefore stable while the conversation only grows; should the live leaf
281
+ move to another branch, earlier seqs can shift and `total` can shrink — a
282
+ consumer that sees `total` below its cursor re-reads from `after=0`.
283
+ - Two passes over the file: one for the `uuid`/`parentUuid` graph (a few
284
+ fields per record), one that projects only live records and redacts and
285
+ clips only the requested window — memory is the graph plus the page, not
286
+ the transcript.
287
+ - `after` returns entries with `seq >= after` (negative → 0); `limit` is
288
+ clamped to `1..500`, default 200; `total` always counts the whole file.
289
+ - Credential shapes are redacted before anything leaves the guest
290
+ (`redact.py`, mirroring the dcode mirror's list): `sk-…` keys, Bearer
291
+ and Basic authorization, `x-api-key` headers, environment-style assignments
292
+ whose name says credential (`…_API_KEY=`, `…_ACCESS_KEY=`, `…_SECRET=`,
293
+ `…_TOKEN=`, `…_PASSWORD=` — the guest's own `ATHENA_*`/`ANTHROPIC_*`
294
+ variables, `AWS_SESSION_TOKEN`, `GITHUB_TOKEN`, …), GitHub/AWS/Slack/Stripe
295
+ shapes, PEM blocks and URL userinfo.
296
+
297
+ ## Tests
298
+
299
+ ```bash
300
+ cd python-sdk/athena-claude-coder
301
+ pip install -e ".[dev]"
302
+ python -m pytest -v --tb=short && ruff check . && ruff format --check .
303
+ ```
304
+
305
+ The suite never spawns a Claude process: a fake `ClaudeSDKClient` drives the
306
+ runner, and the protocol contract is exercised through the real
307
+ `langgraph_sdk` client on an ASGI transport. Tests marked `post_merge` need a
308
+ real Claude and are skipped unless `ATHENA_CODING_WORKER_POST_MERGE=1`.