71 Commits
Author SHA1 Message Date
David Gageot 8378f13506 fix: restore agent limits when resuming A2A sessions
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-10-07 14:32:30 +02:00
David Gageot 3726dec7c5 test: cover ACP MCP and A2A runtime configuration
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-10-07 14:10:18 +02:00
hjeddad 9eb3205a6e test: cover remaining hook routing acceptance gaps
Add tests for the routing behaviours that had code but no coverage:

- budgets: a real token budget, or one already spent, stops the run; an
  assessment that exhausts the budget never falls back to the default agent,
  for both entry and completion routing
- accounting: negative, NaN or infinite cost and negative or overflowing token
  counts fail closed; unknown usage still routes and is flagged as unpriced
- structured output: an accepted tool-mode answer reaches after_agent_complete
  with the validated JSON; a rejected one never routes
- evaluator scope: an agent's own evaluator wins over the team's, a bound scope
  never borrows the team's, and an imported agent keeps its own bindings
- headless: cli.Run in text and JSON mode, including continuation, low
  confidence fallback and a non-zero exit when a decision fails
- entry points: serve api (decision streamed and stored), A2A, the
  OpenAI-compatible chat server and MCP all use unpinned sessions that route
- native TUI: a finished draft continues to the reviewer

Extract newRoutingFixtureFor so a fixture can use the completion event and
extra runtime options, and let scriptedProvider return a scripted stream.
2026-10-01 15:43:57 +02:00
David Gageot db5746fd0d fix: drain runtime streams before releasing turn ownership
Cancel abandoned runs and drain their event channels before returning.
Keep API streaming locks and ACP turn tokens held until teardown
finishes; apply the same lifecycle contract to A2A, CLI, and
synchronous runtime wrappers. Regression tests cover cancellation,
handler errors, and follow-up turns.

Signed-off-by: David Gageot <david.gageot@docker.com>
Assisted-By: Claude (Anthropic)
2026-09-18 15:18:19 +02:00
Arnaud Héritier 085611ed83 feat(#3996): persist absolute WorkingDir provenance at every local creation path
Every session created against a local workspace now records the absolute
workspace root that owns it, so generated files can later be resolved
against the owning workspace instead of whatever cwd the viewer happens
to run from:

- delegated/nested sub-sessions inherit parent.WorkingDir
- serve mcp / a2a / chatserver capture the server workspace once at
  startup instead of reading os.Getwd per request (or not at all)
- embeddedchat conversations and `docker-agent new` capture the
  configured root or the creation-time cwd
- --working-dir writes the absolutized path back into RuntimeConfig so a
  relative flag value cannot leak into persisted provenance
- compaction runs inherit the compacted session's workspace

Remote/headless surfaces (remote backend template, API templates without
working_dir, ACP without client cwd, evaluation container transcripts)
intentionally keep an empty WorkingDir and now document that choice.

session.ResolveWorkingDir adds a bounded, cycle-safe, validated
parent-chain fallback for old persisted sub-sessions with an empty
WorkingDir; it rejects relative/malformed roots and never falls back to
the process cwd. It is the primitive the generated-media resolver will
build on.
2026-09-09 17:55:34 +02:00
David Gageot d374c452a9 chore: address lint findings in the source split 2026-09-04 09:23:10 +02:00
David Gageot 1d2224aa23 refactor(config): split agent sources so embedders link only the source types they use
- config.Load is now YAML-only. HCL support becomes a source decorator,
  hcl.NewSource, applied by the resolver; embedders wrap sources that may
  carry HCL themselves.
- The OCI source moves to pkg/config/ocisource (ocisource.New).
- Resolve, ResolveSources, ResolveAlias and BuiltinAgentNames move to
  pkg/config/sources together with the embedded built-in agents. This is
  the package that knows every source type.
- teamloader no longer hardwires the resolver for external sub-agents:
  WithSourceResolver supplies it, and loaderdefaults.Opts wires
  sources.Resolve so CLI behaviour is unchanged.

pkg/config drops hcl, go-cty, levenshtein, textseg, wordwrap,
klauspost/compress and image-spec, and only reaches
go-containerregistry/pkg/name for reference parsing.
2026-09-04 09:21:52 +02:00
Arnaud Héritier 4e7f87e896 fix(runtime): deliver StreamStopped with a bounded blocking send
finalizeEventChannel emitted the terminal StreamStoppedEvent non-blockingly
and dropped it whenever the runtime's 128-slot event buffer was full at
teardown. Under normal streaming back-pressure (a consumer, e.g. the
PersistenceObserver, doing a synchronous per-delta SQLite write) the buffer
is routinely full at that exact moment, so the TUI/supervisor/leantui never
saw the event and stayed stuck on "Working…" until Esc (#4136).

Replace the non-blocking emit with a bounded blocking send (~5s deadline,
event_sink.go's new boundedChannelSink/bounded()): a consumer still
draining the channel reliably receives it, even past context cancellation
(the TUI keeps draining after Esc); it is dropped only once nothing
accepts it within the deadline, i.e. the consumer has genuinely abandoned
the channel. Deliberately not unbounded: a consumer audit found several
call sites that legitimately stop reading before close (a2a, acp, cli
runner, LocalRuntime.Run, embeddedchat, server RunSession), and an
unbounded send there would reintroduce the #3070 teardown deadlock that
#3074/#3275 previously fixed by making the emit non-blocking in the first
place.

Also harden pkg/a2a/adapter.go: it only completed the ADK turn from the
StreamStoppedEvent case, so an ever-dropped event left the turn hanging.
Add a channel-close fallback so a closed events channel without a prior
StreamStopped still completes the turn.
2026-09-03 17:53:33 +00:00
Sayt0 262a1116b2 Merge pull request #4041 from docker/chore/a2a-adka2a-v2
chore(a2a): migrate from adka2a to adka2a/v2
2026-08-31 10:06:59 +02:00
Sayt-0 52ea6d7ae9 test(a2a): apply invocation context deltas in fake 2026-08-24 15:14:45 +02:00
Sayt-0 b0b0d236aa Merge origin/main into chore/a2a-adka2a-v2 2026-08-24 14:34:07 +02:00
Sayt-0 e8cfe567bb chore: migrate to OpenTelemetry SDK 1.45 2026-08-24 10:56:20 +02:00
Sayt-0 beb49cf73a chore(a2a): migrate to adka2a v2 2026-08-24 09:38:12 +02:00
Arnaud Héritier 11d5f983d4 feat(a2a): require explicit security for network listeners 2026-08-18 14:09:58 +02:00
Arnaud Héritier 6af3259011 test(a2a): cover unattended tool-policy paths over JSON-RPC 2026-08-18 14:09:57 +02:00
Arnaud Héritier 008fb5ac2b refactor(a2a): extract server assembly from Run 2026-08-18 14:09:57 +02:00
Arnaud Héritier 9df38cd035 fix: isolate A2A session resumes by origin 2026-08-18 14:09:57 +02:00
Arnaud Héritier 6d5d317e5c feat(a2a): restrict unattended tool execution 2026-08-18 13:30:25 +02:00
Sayt-0 24fa4d7b91 fix: close Windows test resources 2026-07-30 14:03:19 +02:00
Trung Nguyen 934784b7e3 feat: three-mode safety policy (strict / balanced / autonomous) with native shell classification
Tool approval today is a coarse two-option choice: approve a call once,
or approve everything for the rest of the session. Prompt fatigue
pushes people to the blanket option without them realising it also
covers destructive commands. There is no middle ground, and no obvious
way back. The shell classifier (safer: true) was also entangled with
the approval verdict, which made its judgement impossible to reuse.

Introduce three explicit safety modes and split the classifier into a
pure labeller so the mode is what actually gates the call:

               safe     destructive   unknown
  strict       ask      ask           ask
  balanced     ALLOW    ask           ask
  autonomous   ALLOW    ALLOW         ALLOW

Every call gets a safe / destructive / unknown label from a new
dependency-free pkg/safety package (shell commands via the pattern
taxonomy, other tools via their MCP annotations), and the pipeline
becomes: preempt hooks -> custom rules -> mode x label -> default
hooks -> ask user. Custom rules always win over the mode; session
ask rules always prompt while agent-author ask rules yield to a
user-chosen auto-approving mode.

Escalation is guided and reversible: confirmation prompts offer the
next rung, a new --safety flag and the PATCH safety-policy endpoint
set or undo the mode at any time, and downgrading genuinely revokes
the blanket approval (the yolo toggle restores the pre-escalation
mode, surviving branch and fork).

Compatibility: legacy policy values (unsafe / safer / safe-auto) and
resume verbs are accepted and normalized; safer maps to balanced,
which is deliberately narrower (unknown commands now prompt). Sessions
that never choose a mode keep the historical default. The shell
toolset's safer: flag stays parseable but is ignored; the safer_shell
builtin degrades to a metadata-only labeller and warns at config build
time. Confirmation events and the on_tool_approval_decision hook now
carry safety_label. Resuming a session stored as autonomous keeps
autonomous without re-passing --yolo; an explicit --safety on resume
overrides.
2026-07-27 10:10:48 +02:00
David Gageot a5637e9619 refactor(session)!: move file-backed SQLite store to session/sqlitestore
pkg/session mixed the in-memory store with the file-backed constructor,
so every embedder of pkg/runtime linked modernc.org/sqlite (large and
slow to compile) even when using purely in-memory sessions.

session.NewSQLiteSessionStore(ctx, path) becomes sqlitestore.New(ctx,
path); behavior (open, migrate, backup+retry recovery) is unchanged.
SQLiteSessionStore, NewSQLiteSessionStoreFromDB and the migrations stay
in pkg/session - they only use database/sql and don't drag the driver.

BREAKING CHANGE: callers of session.NewSQLiteSessionStore must import
github.com/docker/docker-agent/pkg/session/sqlitestore and call New.
2026-07-21 15:02:51 +02:00
Sayt-0 ac18222479 fix(session): lock title and usage scalar access 2026-07-15 15:46:12 +02:00
Sayt-0 6ac3210c4d test: raise input and protocol package coverage 2026-07-13 17:17:48 +02:00
Sayt-0 7ca74ebd43 feat(runtime): cap oversized tool results 2026-07-10 13:41:33 +02:00
David Gageot dbbf146349 consolidate AppName constant in pkg/version and drop legacy cagent tracer name 2026-07-01 17:54:05 +02:00
David Gageot 69231c755e test: add t.Parallel() to eligible tests
Assisted-By: Claude
2026-06-29 13:39:47 +02:00
David Gageot 0a87da1f97 refactor: thread context through DB and test helper setup
Thread context through sqlite/session/RAG/memory DB setup so constructors and migrations no longer mint fresh roots. Fake proxy cleanup and askpass connection handling now use context providers derived from their startup context. LocalRuntime stores a context provider for tool-change refresh callbacks.

The remaining suppressions are intentionally detached event hooks/MCP supervisor callbacks or compatibility fallbacks.
2026-06-29 10:17:44 +02:00
David Gageot ae5a574438 refactor(runtime): require context for runtime construction and title generators
Thread context into runtime.New/NewLocalRuntime so constructor-time model validation no longer needs context.TODO. Also add context to Runtime.TitleGenerator so title-model selection is correlated with the caller context.

Update runtime constructors across CLI/server/examples/tests and pass context through existing call sites.
2026-06-29 10:17:44 +02:00
Tomas Dabašinskas c4bc2b5874 feat(otel): instrument A2A server with otelhttp and gen_ai.invoke_agent semconv
- `pkg/a2a/server.go`: wrap the agent-card and JSON-RPC endpoints with `otelhttp.NewHandler` so inbound A2A requests extract `traceparent` / `tracestate` / `baggage` and emit a SERVER span. The outer `agent-a2a` server wrap covers any auxiliary routes
- `pkg/a2a/adapter.go`: in `runDockerAgent`, decorate the active SERVER span with `gen_ai.operation.name=invoke_agent`, `gen_ai.agent.name`, and `cagent.agent.name`. Wires the runtime tracer scope so per-invocation `runtime.session` / `runtime.stream` / `runtime.tool.call` chain onto the inbound A2A span instead of starting fresh trace ids per request
2026-06-22 12:41:09 +03:00
David Gageot cf5a430d2a refactor: make toolsets and providers explicit 2026-06-20 12:27:49 +02:00
maxcleme 50b49a746e feat(a2a): persist sessions to SQLite and resume by contextID
Add a --session-db flag to the a2a command and back the A2A server with
a SQLite session store. The A2A contextID is reused as the docker-agent
session ID so subsequent invocations resume the same conversation.

Signed-off-by: maxcleme <maxime.clement@docker.com>
2026-05-11 20:05:27 +02:00
Tomas Dabašinskas 5f0a5796cd refactor(logging): pass context to all slog calls for correlation
Replace all slog.Debug/Info/Warn/Error calls with their *Context variants (slog.DebugContext, slog.InfoContext, etc.) throughout the codebase. This enables log correlation and tracing by threading context through all logging statements.

Key changes:
- Update all logging calls in runtime, agent, provider, and tool packages
- Pass context.TODO() in cases where no request context exists (e.g., config validation, debug commands)
- Update agent.Model() signature to accept context parameter for log correlation
- Maintain existing log levels and messages; only add context parameter

This change improves observability by allowing logs to be correlated with traces and requests, making debugging distributed operations easier.
2026-05-06 16:42:35 +03:00
David Gageot 4dbca9911b Merge pull request #2505 from cogvel/fix/otel-tracer-mcp-server
fix(runtime): add OpenTelemetry tracer to runtime initialization
2026-05-04 19:31:03 +02:00
David Gageot e8d13bed87 stop hard-coding "root" as the default agent name 2026-04-28 15:46:01 +02:00
Tomas Dabašinskas 9d11df933c fix(runtime): add OpenTelemetry tracer to runtime initialization
Initialize runtime instances with an OpenTelemetry tracer named "cagent" to enable distributed tracing across agent invocations.

This ensures consistent tracing instrumentation in:
- Agent-to-agent adapter runtime
- MCP server tool handler runtime
- Session manager runtime
2026-04-24 14:36:04 +03:00
Joshua Barrington cb623bb798 Fix: allow tab auto-complete without executing 2026-04-13 14:15:16 +02:00
David Gageot cffbdfc78d Merge pull request #2208 from cogvel/fix/auto-stop-max-iterations
feat(runtime): add auto-stop for max iterations in non-interactive mode
2026-04-07 09:40:42 +02:00
Christopher Petito 02348bf161 exempt background-agent polling from loop-termination detection
Signed-off-by: Christopher Petito <chrisjpetito@gmail.com>
2026-04-04 16:35:50 +02:00
Tomas Dabašinskas 039447fe7e feat(session): add non-interactive mode flag to distinguish from tools approval
- Add `NonInteractive` field to Session struct to explicitly mark sessions running without user interaction
- Update runtime loop to use `NonInteractive` instead of `ToolsApproved` for auto-stopping logic
- Set non-interactive mode in A2A adapter, evaluation framework, and MCP server
- This separates the concept of approved tools from non-interactive execution context
2026-03-26 08:56:10 +01:00
Guillaume Tardif 2025cf9d39 feat: make maxOldToolCallTokens configurable in agent YAML
Add max_tool_call_tokens configuration option to control how much
historical tool call content is retained in the context. This helps
manage context size in long-running sessions.

Changes:
- Add maxOldToolCallTokens field to AgentConfig (pkg/config/latest/types.go)
- Update JSON schema with new max_old_tool_call_tokens property
- Add maxOldToolCallTokens field and getter to Agent struct
- Add maxOldToolCallTokens field to Session struct
- Update GetMessages() to use configured value or fall back to
  DefaultMaxOldToolCallTokens (40000)
- Propagate maxOldToolCallTokens through all session creation points:
  - A2A adapter
  - ACP agent
  - MCP server
  - Runtime agent delegation (transfer_task, sub-sessions)
  - HTTP/API server session manager

Configuration semantics:
- Not set or 0: Use default (40000 tokens)
- Positive value: Use that specific limit
- -1: Unlimited (no truncation)

When max_old_tool_call_tokens is exceeded, older tool calls have their
content replaced with "[content truncated]" to keep context size
manageable. Tokens are approximated as string_length/4.

Assisted-By: docker-agent
Signed-off-by: Guillaume Tardif <guillaume.tardif@gmail.com>
2026-03-20 11:15:22 +01:00
Derek Misler 1ed6ba0347 feat: add framework-level tool loop detection to prevent degenerate agent loops
Add a consecutive duplicate tool call detector that terminates agents
when they issue N identical calls in a row without making progress.
This prevents runaway loops like the one observed in pinata run
23141381851, where a sub-agent called read_file 629 times on the same
file, consuming the entire 40-minute timeout.

Default threshold is 5, configurable via max_consecutive_tool_calls
in agent YAML.
2026-03-16 09:39:56 -04:00
David Gageot d7e406c9bf Enable modernize and perfsprint linters, fix all findings
Add modernize and perfsprint to .golangci.yml and resolve all lint
issues across 97 files:

- Replace fmt.Errorf with errors.New where no format verbs are used
- Replace fmt.Sprintf(%d) with strconv.Itoa for int conversions
- Replace redundant fmt.Sprintf(%s) with direct string values
- Use slices.Contains instead of manual loops (SkillsConfig.HasLocal)
- Use omitzero instead of omitempty on struct fields (time.Time, SkillsConfig)
- Inline boolPtr helpers to new(bool) and remove unused functions
- Replace string concatenation in loops with strings.Builder
- Use fmt.Fprintf instead of WriteString(fmt.Sprintf(...))

Assisted-By: docker-agent
2026-03-10 11:57:30 +01:00
David Gageot cb001a241b Remove package to github.com/docker/docker-agent
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-03-09 12:04:59 +01:00
David Gageot 98936b69f6 Rename cagent to docker-agent
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-03-09 11:42:22 +01:00
David Gageot 5aed75d972 Fix cobra command and rename more things from cagent to docker agent
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-03-07 19:20:39 +01:00
David Gageot eb337cf434 Move a2a, acp, mcp and api commands under cagent serve (breaking change)
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-02-17 07:51:28 +01:00
David Gageot ee47fa04e1 Fix A2A agent card advertising unroutable wildcard address
Replace wildcard listen addresses (::, 0.0.0.0) with localhost in the
agent card URL so clients can actually reach the server. Also switch
TCP listeners to tcp4 to avoid binding to IPv6 on dual-stack systems.

Fixes #1679

Assisted-By: cagent
2026-02-10 19:24:54 +01:00
David Gageot c65462ac9e Support using agent yamls from private github repos
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-02-04 19:52:08 +01:00
David Gageot ab55b90ff7 Update github.com/a2aproject/a2a-go v0.3.3 -> v0.3.4
Update the Read() method signature to include TaskVersion as required
by the new API. Also add WriteVersioned() method to satisfy the Queue
interface.

Assisted-By: cagent
2026-01-16 08:02:49 +01:00
David Gageot aeb25de922 deps: upgrade github.com/labstack/echo/v4 to v4.15.0
Assisted-By: cagent
2026-01-05 12:46:36 +01:00