Skip to content

release: D1–D8 — durable ACP runtime, dana-code on AgentSession, first-contact DX - #30

Merged
ngoclam9415 merged 107 commits into
masterfrom
develop
Sep 3, 2026
Merged

ngoclam9415 merged 107 commits into
masterfrom
develop

Conversation

@ngoclam9415

Copy link
Copy Markdown
Contributor

What's in this release train

105 commits · 195 files · +35,380 / −2,900 — the full D1–D8 delivery from feat/acp-agent-session-kernel.

D1 — Durable Conversation

Session Journal (SQLite + PostgreSQL) as the sole durable authority; restart-resumable ACP agent; input facts durable before model output; session replay.

D2–D6 — ACP phases

  • D2 Tool Catalog, visible cancellable tools, cancellation trees, durable jobs
  • D3 Autonomous permission policy — modes, grants, preflight, hard-deny-wins, OwnerScope isolation
  • D4 Configured model switching — model catalog, atomic rebinding, /model
  • D5 MCP tools — stdio + HTTP transports, leases, session restore, per-tool dispatch + per-tool policy
  • D6 Multimodal content — normalization, MIME/size checks, artifact retention, ACP attachments

D7 — dana-code on AgentSession

CLI rewired onto the host-neutral AgentSession core in-process (Option B) — durable conversations, tool catalog, permission prompts, model switching, MCP, multimodal in the terminal; dana-code ↔ dana-acp parity.

D8 — First-Contact DX (from an external engineer's hands-on review)

  • README truth pass (every sample executed) + MIT LICENSE
  • All six CLIs answer --help/--version; zero import-time side effects (lazy log dir, skills scan opt-in via DANA_CLAUDE_SKILLS=1)
  • Fresh-clone make test green (env guard; 2728+ passing); two-lane CI with real-postgres journal contract on master PRs
  • STARAgent.__getattr__ magic removed (unknown attrs raise AttributeError)
  • AgentSession.create() + flat imports + session-id reconciliation; 16-line hello-world host (docs/examples/host_hello.py)
  • Distribution renamed dana-agent; tag-triggered OIDC publish workflow (fail-closed; tag must match pyproject version)

Verification

  • Fresh clone uv sync && make test: 2728 passed / 221 skipped / 1 xfailed (independently reproduced twice)
  • Session-journal contract: 32/32 against real postgres:16
  • Clean-venv wheel install: dana-agent --version → dana-agent 0.2.0; twine check PASSED
  • Every story passed fresh-context story review + formal code-review gate + user acceptance

⚠️ Before merging

The bump-version bot will bump develop to 0.2.1 on PR open. On merge, the release train tags v0.2.1 and publish.yml uploads to PyPI — configure the dana-agent Trusted Publisher on pypi.org first (repo aitomatic/dana-runtime, workflow publish.yml, environment pypi) or the publish job will fail (re-runnable after config).

httpx<0.28 blocked dana-agent[langsmith]'s websockets>=15 in a single
uv workspace. anthropic==0.30.1 caps httpx<0.28, so the two must move
together: anthropic -> >=0.40.0 (first release supporting httpx 0.28.x;
the API surface dana uses is stable through 0.116).

- requires-python: >=3.12 -> >=3.11,<3.14
- llama-stack gated with python_version>='3.12' marker (needs >=3.12)
  so [local] resolves on 3.11 (ollama-only)

Validated via live provider tests; no new unit-test regressions.
chore(deps): bump httpx>=0.28.1 and anthropic>=0.40.0
@observable dispatches to langsmith.traceable when LANGSMITH_TRACING=true
or DANA_LANGSMITH_ENABLED truthy; exclusive with Langfuse (LangSmith wins).
No call-site changes — all 30+ @observable sites traced automatically.

- observable.py: LANGSMITH>LANGFUSE>noop dispatch, _langsmith_kwargs adapter
  (as_type->run_type, session_id/user_id folded into metadata), bare-form
  normalization to avoid double-call, factored _langfuse_wrap preserves
  byte-equivalent flush behavior on the langfuse path
- init_environment.py: _install_langsmith_shim mirrors the langfuse shim so
  the module imports cleanly without the extra installed
- pyproject.toml: langsmith>=0.8,<0.9 added to [observability] extra
- tests: 11 cases incl. SC7 langfuse flush regression (sync/bare/async) and
  async exclusivity
- docs: tracing-backends section + silent no-op caveat + changelog entry

Enable: pip install dana[observability]; LANGSMITH_TRACING=true LANGSMITH_API_KEY=...
feat(observability): add LangSmith as alternative tracing backend
fix(observability): sanitize LangSmith trace payloads
feat(prompt): add public system prompt template override
Add DanaACPAgent implementing the ACP Agent protocol over stdio JSON-RPC,
enabling dana-console to connect to Dana as a Custom ACP Agent.

- dana/apps/acp/agent.py: DanaACPAgent translating ACP calls (initialize,
  session/new, session/load, session/resume, session/prompt, session/cancel)
  to AgentSession operations, streaming HostEvents back as session_update
  notifications
- dana/apps/acp/translation.py: HostEvent → ACP update chunk translation
  (ACP types never enter STAR core)
- dana/apps/acp/__main__.py: entry point with stderr-only logging
- dana/__init__/init_environment.py: redirect structlog to stderr so stdout
  stays clean for JSON-RPC frames
- pyproject.toml: dana-acp console script entry point
- tests/integration/test_acp_agent.py: 14 in-process + subprocess tests
  covering load capability, replay-before-return, chunk streaming, burst
  ordering, busy, cancel, malformed content, stderr/stdout discipline
Untrack /sprint/, /v2/, CLAUDE.md (local-only working docs, consistent with existing AGENTS.md/.claude/.opencode ignores). CLAUDE.md removed from repo; local copy retained via gitignore.
Per-agent intercept-capable EventBus: first-wins aggregation, sync+async handlers, raise isolation, emit_sync via Misc.safe_asyncio_run. Lazy mount on BaseSTARAgent. Non-dict results warned+skipped. 17 tests.
.agents/, .codegraph/, .codex/, .superpowers/, memories/, tests/unit/core/guard/ — local tool artifacts (consistent with .claude/.opencode).
_build_native_tools_if_supported now returns early if schemas already built. Rebuilding every build_prompt re-ran inspect.signature on every resource method under the tracing chain, exhausting the recursion budget on long sessions (librarian console crash). Structural deps don't change per turn -> build once. Adds 2 tests.
Milestone M3 (longest pole of v2.0 extensibility backbone). Wires the S1
EventBus into both tool-execution paths and adds a deny-only PermissionPolicy.

- ext/operation.py: Operation + ToolIdentity (thin, read-only via MappingProxyType)
- ext/permission.py: PermissionPolicy deny-only (a tool_call subscriber)
- ext/guard.py: reference rm-rf + protected-path policy (S4 discovery target)
- tool_executor.py: emit tool_call/tool_result around dispatch in both single-
  call paths; split out _dispatch_single_call[_async] (dispatch NOT merged)
- runtime/{protocols,__init__}.py: remove dead ToolHookProtocol/ApprovalProtocol
  scaffold + constructor params hooks/approval (never wired)

Adversarial review fixes: non-dict tool_result modify no longer crashes the
batch (isinstance guard + warn); guard substring rules str()-coerce (defeats
list-arg bypass); strict-bool block (is True); Operation.arguments immutable.

Tests: 27 new (S3.1-S3.15 + 7 adversarial fix-regressions). Regression green:
tests/unit + tests/integration 1614 passed, 37 skipped, 1 xfailed.
Milestone M2. Emits see_end/think_end/act_end/reflect_end around the STAR
phases in query()/aquery() so handlers can observe, modify, or block each
phase. STAR contract (_see/_think/_act/_reflect) unchanged.

- base_star_agent.py: _emit_phase[_async] helpers + per-phase block/modify
  wiring in _do_query/_do_aquery; reflect_end emit in the reflect wrappers.
- Orchestrator-based wiring (not scatter-site): STARAgent._think/_act_async
  broadcast inline without super(), so base-site wiring would miss them.
- Fix latent S1 bug: event_bus property used getattr(self,_event_bus,None)
  but STARAgent.__getattr__ returns a magic-method stub for any unknown attr,
  so the bus was never created on the real agent. Now reads self.__dict__.
- Adversarial fixes: per-phase exit uses EXIT_FLAG is True (not
  _do_exit_star_loop, avoiding the empty-dict false-exit quirk); act_end
  block sets phase_blocked (skips reflect, prevents repeat); non-dict modify
  ignored + warned.

Trade-off: broadcast fires before emit, so legacy broadcast observers see the
pre-modify result (accepted for minimal blast radius; modify still changes the
result for later phases).

Tests: 10 new (T2.1-T2.8 + 2 adversarial). Regression: tests/unit +
tests/integration 1624 passed, 37 skipped, 1 xfailed (the 9 done-flag-autonomy
tests caught the event_bus bug pre-fix).
Milestone M4 — completes the v2.0 extensibility backbone (M1-M4 all shipped).

Drop-in Python extensions discovered from ~/.dana/extensions/ (global, always)
and .dana/extensions/ (project, trust-gated via DANA_TRUST_PROJECT_EXTENSIONS)
and bound to the agent's EventBus via a setup(agent) factory using agent.on().

- ext/extensions.py: ExtensionManager — discover/load/reload + LoadReport.
  * Per-agent, lazy via agent.extensions (__dict__ storage, same __getattr__
    lesson as S1/S2).
  * Loader bypasses the pyc cache (read_text+compile+exec): SourceFileLoader
    keys .pyc on (mtime,size) so a same-byte-size edit within 1s would exec
    stale code — fatal for hot-reload correctness.
  * Reload: unsub tracked handlers, pop stale sys.modules, re-exec, emit
    SESSION_RELOAD. Must run at idle (S1 Finding A).
  * Sub tracking via wrapping bus.subscribe during setup (try/finally).
- base_star_agent.py: agent.on alias + extensions property + load_extensions/
  reload_extensions delegates. NOT auto-loaded at construction (host calls it;
  zero regression risk to agent init).
- Trust gate: global = user's home (trusted); project = explicit flag.

Adversarial fixes: failing setup rolls back partial handler registrations
(transactional; was a reload leak); reload pops stale sys.modules entries.

Tests: 10 new (T4.1-T4.8 + 2 adversarial). Regression: tests/unit +
tests/integration 1634 passed, 37 skipped, 1 xfailed.
- README quickstart/usage (6 sites): agent.process/agent.stream_response
  -> agent.aquery / agent.aquery_text_stream (real documented surface)
- docs/code-standards.md: test example + concurrency example off phantom
  'process' name
- base_star_agent.py: trim stale __getattr__-bypass docstring rationale
  (workaround __dict__ code kept; harmless defensive style)

Suite: 2128 passed, 21 skipped, 1 xfailed (tests/unit); full tests/ 2744
passed. rg sweep clean of magic-dispatch call-site reliance.
ClaudeCodeSkills construction no longer scans skills_dir (default
~/.claude/skills) or probes the Claude CLI unless DANA_CLAUDE_SKILLS=1
is set. Garbage values disable discovery and log a warning. Knob
documented in module and class docstrings.
Default STARAgent construction performs no skills scan: the loader is
not even constructed unless the opt-in env var is set. Documented in the
enable_skills arg docstring.
- default construction: zero discovery/availability calls (resource and
  agent-level mocked loader)
- DANA_CLAUDE_SKILLS=1 restores discovery
- garbage knob value -> off + warning
- existing discovery tests and integration/live suites updated to opt in
…dir)

Approved scope addendum (orchestrator ruling): importing dana used to mkdir
~/.dana/logs via module-level get_debug_logger() in dana/common/llm/llm.py.
Resolve log_dir/paths lazily on first use so --help/--version stay
side-effect free; logging behavior when actually used is unchanged.
New dana/apps/cli_flags.py: standard_parser(prog, description, setup)
builds a stdlib argparse parser with --version (importlib.metadata with
pyproject fallback). Called FIRST in every console-script main():

- dana-agent, dana-code, dana-init, dana-acp: parse before any agent/env work
- dana-agent-repl: keeps -v/--verbose via setup hook
- dana-memory: parser now built via standard_parser; dependency check moved
  after parse so --help/--version answer even without dana[memory] extras

--help/--version exit 0 before constructing agents; unknown flags get the
argparse usage error (exit 2), never swallowed into a conversation. Bare
invocation path unchanged.
…empty-home

All six console scripts: --help exits 0 with usage and no 'Goodbye',
--version prints the pyproject version, --bogus-flag exits non-zero with
usage on stderr. Plus: --help under an empty HOME creates nothing, bare
dana-code still enters the REPL and exits on EOF, bare dana-memory still
errors on missing subcommand.
…rnalFact, SessionRecord importable from dana.core.session
…scope+SESSION_CREATED in one call, resume on existing id
…d (relabel agent _session_id in _prepare_agent)
…prompt -> print terminal text, DANA_MOCK_LLM=1 for offline
…tent resume, hello-world flow, identity reconciliation, example runs
- Quick start: STARAgent(model=...) + await agent.aquery(message=...) (executed live, output verified)
- CLI table: real [project.scripts] names (dana-agent/-repl, dana-code, dana-memory, dana-init, dana-acp) + --help/--version note (all six verified)
- Badges: version 0.2.0, Python 3.11+ (match pyproject)
- Remove fictional API surface: tools=/max_tokens=/compression_threshold=/timeout kwargs, agent.state.timeline, WebResearchResource, performance table, llm_providers config key (real: llm.providers)
- DANA_MOCK_LLM reframed as make-target/example-script detail, not library feature; drop DANA_DEBUG claim (zero refs in dana/)
- Link AgentSession hello world at docs/examples/host_hello.py (executed with DANA_MOCK_LLM=1)
- API reference: real STARAgent kwargs (max_context_tokens, enable_*), get_state()/get_timeline_summary()
- Dev section: only real make targets
Copyright (c) 2026 Dana Contributors. Makes the README MIT badge/link resolve.
PyPI 'dana' is taken by the unrelated Dana DSL, so the distribution is
renamed to dana-agent while the import package stays dana. Consequences:
- cli_flags.py + acp/agent.py: importlib.metadata version lookups now
  read dana-agent (code_app already did)
- [project.optional-dependencies] full extra self-reference and pip-install
  hint comments updated to dana-agent (old string resolved against the
  foreign PyPI 'dana' and broke uv lock)
- uv.lock: project entry renamed (2-line mechanical diff)

Addendum 1 (pre-approved): cli_flags dist-name string.
Addendum 2 (supervisor ruling): acp/agent.py:73 same one-string fix.
Addendum 3 (mechanical, flagged): full-extra self-reference + uv.lock.
PEP 639 SPDX license expression ('license = "MIT"') requires
setuptools>=77; >=42 only worked via isolated builds that pulled latest.
Folds in the Minor flagged in formal code review.
publish.yml fires on the v* tags that release-on-merge-to-master.yml
already pushes. Verifies tag == pyproject version, builds sdist+wheel,
twine-checks metadata, uploads via pypa/gh-action-pypi-publish with OIDC
(no stored tokens). Manual dry-run via workflow_dispatch (default) and
test.pypi.org target included. One-time maintainer step documented in the
header: configure the dana-agent Trusted Publisher on pypi.org.
Append-only: adds the PyPI install path alongside git clone, honestly
marked pending-first-release until the maintainer completes the Trusted
Publisher setup (dist renamed to dana-agent; import package stays dana).
…n diagram, dead magic guards, gitignore .worktree
…x python

The [postgres] parametrizations of test_session_journal_contract.py fail
(not skip) when CI=true and DANA_TEST_POSTGRES_DSN is unset — by design
('the real-database contract must be exercised in CI') — but the workflow
never provided a database, so every PR run died at setup with 10 errors
(Event loop is closed masking the intended pytest.fail). Verified locally:
32/32 pass against postgres:16.

Also pins setup-uv to the matrix python — the '(3.12)' job was actually
running CPython 3.13.15.
Fast lane (develop PRs + dispatch): no postgres service, [postgres]
parametrizations deselected via -k "not postgres" (verified: 16 passed,
16 deselected locally). Full lane (PRs to master): postgres:16 service +
DSN, complete suite.
feat: D1 Durable Dana Conversation — restart-resumable ACP agent
chore: bump version to 0.2.1 (release train)
@ngoclam9415
ngoclam9415 merged commit 4d8ccef into master Sep 3, 2026
4 checks passed
@ngoclam9415
ngoclam9415 deleted the develop branch September 3, 2026 11:38
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