Skip to content

feat(agent_sys): AgentsView as the o11y panel - #154

Draft
dorado269 wants to merge 52 commits into
dev.yihou.aiopt.task_packagefrom
dev.yihou.aiopt.more.demo
Draft

feat(agent_sys): AgentsView as the o11y panel#154
dorado269 wants to merge 52 commits into
dev.yihou.aiopt.task_packagefrom
dev.yihou.aiopt.more.demo

Conversation

@dorado269

Copy link
Copy Markdown
Collaborator

Wires AgentsView into agent_sys as its o11y panel. AgentsView is used as an unmodified external dependency; every knob turned is one it already publishes.

What this gives you

Deploying agent_sys brings up a web panel on http://127.0.0.1:18888 showing only the sessions agent_sys itself produced. Your own Claude Code is untouched: ~/.claude is never read, written or reconfigured.

  • Port default 18888, settable with --agentsview-port or AGENTSVIEW_PORT; --no-agentsview disables it.
  • A taken port is one warning and a skip, never a failure. So is a missing binary, a failed install, a dead daemon, a timed-out health check. o11y may never fail the thing it observes — there is a test per path and a bare except Exception at the call site as a structural backstop.
  • Nothing starts for --dry-run or --clean.
  • The binary installs into a dedicated prefix ~/.infera_agent_sys (~/.local-shaped, owned by env_mgr). Nothing lands in /usr/local/bin or ~/.local/bin.

How the panel is scoped — five gates

  1. CLAUDE_CONFIG_DIR into the agent child's environment only, never os.environ.
  2. CLAUDE_PROJECTS_DIR → the prefix, the one root AgentsView scans.
  3. disabled_agents — 59 provider names, measured against the real binary.
  4. A separate AGENTSVIEW_DATA_DIR; a pre-existing ~/.agentsview is never opened.
  5. HOME redirected into the prefix, so every provider computes its default root inside it. This one is structural: a provider upstream adds next release, absent from gate 3's list, still cannot reach ~/.codex. Found by measurement, now held by tests.

<zone>/config/projects is a symlink into the prefix, so material.py's per-attempt config dir keeps its credentials while transcripts land somewhere a resident daemon can read.

Acceptance

Six checks against pinned commit 7d25923, three arms (control / panel / port-busy), each with a prefix created empty and an ephemeral port. Report: ws.agentsview_o11y/acceptance/PINNED-7d25923/REPORT.md.

# check verdict
1 the panel answers on its port PASS
2 the run's transcripts are in the prefix PASS
3 the panel lists that session and no others PASS
4 nothing of ours in ~/.claude PASS
5 exit code matches --no-agentsview PASS
6 a taken port warns once and does not fail FAIL as originally judged; PASS on the replacement criterion — both stand

Check 6's original criterion was wrong and is recorded as such rather than quietly rewritten. It compared exit codes of a package whose verdict depends on model output. Across nine demo2 runs there were three failures, always a content validator, on a different arm each round — and in the final round the arm doing the most o11y work exited 0 while the arm doing the least exited 5. The o11y contract itself held exactly: one warning, correct text, zero tracebacks.

Check 3 was confirmed in a real headless browser against the panel arm's own empty prefix — 3 sessions rendered, all agent_sys's own — not from a JSON body. The panel arm's bin/ was empty at 13:36 and the binary landed at 14:03 from the recipe, so the install path is proven on a machine that had no binary.

pytest tests/2227 passed, 3 skipped, 4 xfailed.

Notable findings about the dependency

  • serve --background --port N ignores --port and exits 0 when a daemon already holds that data dir. Fixed with --replace.
  • agentsview health starts a background daemon on a port AgentsView picks; doctor sync validates identically and starts nothing. AGENTSVIEW_NO_DAEMON=1 was tried and rejected — it makes health refuse outright rather than fall back to a direct read.
  • serve stop only tracks the most recent daemon; orphans are unreachable by any documented command.
  • The default GET /api/v1/sessions hides one-shot sessions, which is every agent_sys agent. The web UI calls sessions/sidebar-index with include_one_shot=true and is unaffected — we spent hours on a non-bug by treating an API response as a proxy for what a person sees.

Known limitation

The zone symlink's behaviour under an enforcing policy is untested, because AGENT_SYS_NO_PERMISSIONS=0 currently fails for every AI package at agent/runner.py:1335 — a pre-existing agent_sys refusal, unrelated to this change and confirmed by a paired control arm with one file differing. Read that as untested because currently untestable, never as safe.

Wire kenn-io/agentsview into agent_sys as its o11y panel: native binary under
a ~/.infera_agent_sys prefix owned by env_mgr, session scoping via a
child-process-only CLAUDE_CONFIG_DIR, resident daemon on port 18888, and
warn-and-skip on every failure mode.

Previous task's CLAUDE.md and checkpoint log preserved as .bak.

Signed-off-by: yihou <yihou@amd.com>
Phase 0 settles the two unverified facts (install prefix knob, whether
claude-agent-sdk honours CLAUDE_CONFIG_DIR) before any dependent code.
Phases 1-5 are TDD tasks; Phase 6 is the six-check acceptance on demo2.

Signed-off-by: yihou <yihou@amd.com>
…is ours

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Pins the release (v0.42.0, checksum-verified via a private mktemp -d,
never a shared /tmp path) rather than following "latest"; version:
"0.42.0" so check_cmd's output is compared against something instead
of satisfies(actual, None) accepting anything.

target.path is a placeholder per the existing sglang.repo.yaml
convention: neither recipe.py nor the bin installer expand ${VAR} in
a YAML value, so the prefix is supplied via --path at invocation, not
templated in the recipe file. install: expands $AGENT_SYS_HOME (the
one var the documented invocation exports) rather than introducing
$AGENT_SYS_BIN, which nothing in this call chain sets.

Also notes in the design doc that the claude CLI lazily creates
.claude.json, backups/, and sessions/ as siblings of projects/ under
the redirected CLAUDE_CONFIG_DIR root, measured during Phase 0 recon.

Signed-off-by: yihou <yihou@amd.com>
…ment

agent_environment() does not put \$AGENT_SYS_BIN on PATH for spawned
agent children (bin_on_path=False, prepare.py) -- the recipe comment
claimed the opposite. Whatever eventually calls ensure_installed()
still needs \$AGENT_SYS_HOME/bin on PATH itself.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Never raises, one log.warning per failure, same law ensure_running
already holds. Reuses level_for_missing (via importance: suggested in
the recipe) rather than re-deriving severity: the outcome's own
.level decides whether a missing agentsview is fatal.

check_cmd is now an absolute $AGENT_SYS_HOME/bin/agentsview path
rather than a bare command name -- measured by actually running the
recipe three times: a bare name only finds the binary, and thus only
reports "already present (skip)" on a second run, if $AGENT_SYS_HOME/bin
happens to already be on the caller's ambient PATH, which nothing in
this call chain guarantees (agent_environment() deliberately does not
put $AGENT_SYS_BIN on PATH for spawned agent children).

$AGENT_SYS_HOME is patched into os.environ for exactly the duration of
the underlying subprocess calls (run_cmd has no env= parameter) and
restored in a finally, verified to hold even when the installer
raises. Noted in the docstring as the one caller not yet justifying a
shared env= parameter on run_cmd itself.

ensure_installed takes the recipe-running step as an injected callable
rather than calling load_recipe/runner.run directly: env_mgr spec §9's
decoupling wall forbids o11y from importing the installer machinery,
enforced structurally by test_imports.py -- a first draft that
imported recipe/runner directly failed exactly that test. The caller
(left to whoever wires the CLI call site) assembles the callable from
outside the wall.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
…e docs table

Five slugs guessed from AgentsView's human-readable provider table were
wrong (claude-cowork, command-code, copilot-cli, cortex-code,
gemini-cli): serve validates disabled_agents and refuses the whole
config on the first bad name, so serve --background exited 1 and the
panel never came up -- warned and skipped correctly, but for the wrong
reason, caught from a real acceptance run's stderr.

Rebuilt OTHER_PROVIDERS from `agentsview doctor sync`'s own "Agent
roots:" report (every provider name this installed binary recognizes),
minus claude, plus aider (independently confirmed accepted by the
config parser though doctor sync prints no root for it in this
version). Verified end to end: the full corrected list loads cleanly
against the real installed agentsview binary via one health call.

Added check_disabled_agents, wired into ensure_installed right after a
successful install: writes the real config and runs one health call
against it, so a future provider rename is a distinct install-time
warning naming the offending slug rather than a generic "serve exited
1" discovered later. One process call against the actual config file,
not one per candidate -- the parser already reports one bad name per
call, the same way a human debugging this by hand would find them.
Never raises, and a bug in the check itself cannot turn a successful
binary install into a reported failure (separate try/except from
install_item()).

No change to ensure_running's failure/retry behavior: still exactly
one warning on the servce-failure path, still no fallback to a
permissive config -- a dead panel remains the correct outcome of a bad
provider list.

Signed-off-by: yihou <yihou@amd.com>
…a hardcoded list

A hand-maintained OTHER_PROVIDERS has two independent failure modes,
not one: a wrong slug kills serve loudly (already fixed once this
round), but a provider *missing* from the list fails silently --
AgentsView happily scans that provider's default directory and adds
its sessions to the panel, defeating gate 3 with no error at all.
Comparing the previous hardcoded list against a fresh `doctor sync`
run found exactly this: grok, hermes, iflow, kiro-ide, qoder, warp,
zencoder and others were real, currently-recognized providers this
list never mentioned.

discover_providers(prefix) asks the installed agentsview binary
directly (`doctor sync`'s own "Agent roots:" report) and parses every
provider name it recognizes, minus claude. This is now the primary
mechanism: a provider renamed in a future release is self-correcting
(the name comes from the binary, not a stale string), and a provider
newly *added* in a future release is picked up automatically the next
time the binary is asked, with nobody needing to remember to update a
constant.

FALLBACK_OTHER_PROVIDERS (the previous OTHER_PROVIDERS, unchanged
list) is kept as a last resort only, used by resolve_disabled_agents
with exactly one warning when discover_providers can't be trusted
(binary missing/crashed/unparseable report) -- never silently, and
never an empty list, since "disable nothing" is the single most
permissive failure mode this path could produce.

write_config now takes the provider list as a parameter instead of
reading a module constant, so it stays a pure, easily-testable writer
regardless of where the list came from. check_disabled_agents is
unchanged in behavior but now mostly guards the fallback path, since
the primary path's correctness follows from asking the binary
directly rather than needing separate validation.

Verified end to end against the real installed v0.42.0 binary:
discover_providers returns 59 names (claude excluded), matches
resolve_disabled_agents with no fallback triggered, and the resulting
config.toml loads cleanly per check_disabled_agents. Diffed against
FALLBACK_OTHER_PROVIDERS: the only difference is "aider" (fallback-only,
consistent with it being accepted-but-rootless in this version) --
zero real providers missing from the dynamic list.

No change to ensure_running's failure/retry semantics: still exactly
one warning on the serve-failure path, still no fallback to a
permissive config.

Signed-off-by: yihou <yihou@amd.com>
Reverts the previous commit's runtime-derived disabled_agents: team
lead's call, and the right one -- deriving the list at runtime means a
silent upstream AgentsView change silently changes what the panel
shows, with no commit, no diff, no review. Worse than a pinned list
that occasionally drifts loudly via a check.

OTHER_PROVIDERS (renamed back from FALLBACK_OTHER_PROVIDERS) is once
again the pinned, hand-maintained, version-controlled source
write_config actually uses. discover_providers (doctor sync's own
"Agent roots:" enumeration) is no longer the write-time source; it now
only feeds check_disabled_agents's completeness direction.

check_disabled_agents now checks both ways a hand-maintained list can
drift from the installed binary, not just one:

- Direction 1 (already existed): a name in OTHER_PROVIDERS the binary
  no longer recognizes -- breaks serve loudly, the way it broke once
  already. Caught by running health against the config we wrote.
- Direction 2 (new, the one that leaks): a name the binary recognizes
  that OTHER_PROVIDERS never lists. This loads with zero error --
  AgentsView just scans that provider's default directory and puts its
  sessions on the panel. Caught by running discover_providers and
  diffing against OTHER_PROVIDERS.

check_disabled_agents now returns every offending name (from either
direction) in one tuple rather than a single Optional[str], so
ensure_installed's one warning at the call site can name all of them
at once regardless of how many were found.

Sanity-checked the completeness direction the same way as the earlier
_patched_environ check: temporarily removed the discover_providers
block, confirmed three tests go red, restored it, confirmed green
again. Verified end to end against the real installed v0.42.0 binary:
write_config(prefix, OTHER_PROVIDERS) + check_disabled_agents returns
an empty tuple.

Unchanged: no silent fallback in ensure_running, exactly one warning
on the serve-failure path with the stderr excerpt.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
The zone-local CLAUDE_CONFIG_DIR stays: relocating it is what removes the
$HOME grant and what harness.harness_env() carries credentials across for.
But it also relocated the transcripts, and the panel reads one fixed
directory -- measured on demo2, nine agent transcripts landed in nine
different <zone>/config/projects/ and the panel showed none.

material.deploy now symlinks that one subdirectory to
$AGENT_SYS_CLAUDE_HOME/projects. Every attempt has a unique cwd, so each
still writes into its own slug subdirectory; sharing cannot collide.
Idempotent, repairs a wrong link, replaces an empty directory (rmdir
refuses a populated one), and never raises -- one warning and the attempt
proceeds.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
…ement

The symlink delivers gate 1 with permissions off, measured on demo2. With
AGENT_SYS_NO_PERMISSIONS=0 nothing traverses it, because agent_sys refuses
every AI task before the executor starts -- a refusal that predates this
feature, confirmed with paired arms differing in one file. The agent_sys
limitation itself is recorded outside this spec; only the consequence for
this design is here, and it reads 'untested because untestable', not
'safe'.

Signed-off-by: yihou <yihou@amd.com>
…= "0s"

Measured: the production daemon self-exited twice with no override
present, matching AgentsView's own documented default (daemon_idle_timeout,
default "20m", documented in configuration.md). Design spec.md promises
the panel "persists across runs" and ensure_running/ensure_installed are
only ever called from the CLI's own startup path -- with the default
idle exit, a user opening the URL an hour after their run ends finds
nothing, which is exactly the case the panel exists for.

write_config now writes daemon_idle_timeout = "0s" alongside
disabled_agents. Confirmed the key does not break config loading
(health --limit 1 against it, rc=0) -- though also confirmed via a
negative control that this alone is weak evidence, since a completely
made-up key is accepted the same way (health tolerates unknown
top-level keys; only disabled_agents is strictly validated). The real
verification is wall-clock: a background daemon started with this key
and zero client traffic, checked after AgentsView's documented 20m
default window has passed. That check is running in the background
(scratch/idle_probe/data_alive, started 2026-09-03T13:03:39Z); result
to follow in PHASE0.md §0.7 and to team lead once it reports.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Team lead asked whether two concurrent agent_sys deployments on the
same box (same prefix, same port, --replace) could kick one another's
daemon out from under it. Measured rather than assumed:

- --replace against an already-settled daemon on the exact same port
  still unconditionally kills and restarts it (not a same-port no-op).
- ensure_running only reaches --replace after port_is_free(port) is
  True, so the only window two deployments can both reach it for the
  same port is a cold-start race (neither daemon up yet).
- A synchronized two-thread race against the real binary landed only
  one actual daemon start; the loser's own serve --replace invocation
  saw the winner's daemon and did not visibly disrupt it in that run
  -- recorded as empirical, not a documented guarantee.
- Either way, both deployments point at the identical prefix, config,
  and port, so a replacement swaps one daemon for a functionally
  identical one serving the same archive: at worst a sub-second
  connection drop mid-restart, never a lasting outage or different
  data shown.

No code change; this is the docstring team lead asked for so --replace
is not later read as an unexamined bug.

Signed-off-by: yihou <yihou@amd.com>
The smoke test proved the session was servable via GET /api/v1/sessions and
we read its documented one-shot exclusion as 'the panel is broken'. It was
not: a real browser loading the plain / renders the session, because the web
UI's session list calls sessions/sidebar-index and sends
include_one_shot=true in its own request. Confirmed by reading a rendered
page, twice.

The load-bearing assertion is now the browser's request. The CLI surface is
pinned separately -- the two endpoints disagree today and a release moving
either default should fail a test, not surprise an operator. Identity is
still checked on the raw endpoint, which is the only one carrying
first_message.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
zonelink measured it: agentsview health (and projects, and session
list) silently autostarts a background serve daemon on a port
AgentsView itself auto-discovers, exactly the "port decision made by
agent_sys, never delegated" rule this whole component exists to hold
-- happening on check_disabled_agents's read-only validation path,
which nobody watching ensure_running would ever notice. Reproduced
independently by counting real `agentsview serve` processes by exact
argv before/after each subcommand: --version and doctor sync start
nothing; health starts one, landing on 8083 in one run because 8080
was already taken by another stray from the same bug.

AGENTSVIEW_NO_DAEMON=1 (documented for exactly this) was measured next
and rejected: it also disables *direct* SQLite reads for health,
projects, and session list, even against an already-populated
database -- the check would go from "silently starts a daemon" to
"always fails", which is worse, not better. Measured, not assumed.

The actual fix: doctor sync validates disabled_agents with the
identical "unknown session provider" error health produces, and never
starts a daemon in either the config-valid or config-invalid case --
confirmed by the same before/after process count. check_disabled_agents
now runs doctor sync exactly once, testing both directions from its
single result: a non-zero exit with that error is direction 1
(renamed/removed provider); a clean exit's own "Agent roots:" report,
diffed against OTHER_PROVIDERS, is direction 2 (added provider we
never listed). health is not called at all anymore.

Trade-off, recorded in the docstring: a doctor sync that fails never
reaches the point of printing Agent roots, so a config with both a
stale entry and a missing one only reports the stale one per call --
fixing it and re-running finds the missing one next. The two-probe
design could report both problems from one call; traded that away for
never starting a daemon on a validation path.

Found and cleaned up while investigating: three orphaned agentsview
daemons already existed on the real shared prefix (ports 8080/8081/8082,
none on our intended 18888), accumulated from repeated triggering of
this exact bug across the team's testing. `serve stop` only knew about
the most recently tracked one; the other two were unreachable by any
documented command (verified: `serve status` reported only one, and
after stopping it, "No agentsview server is running" despite two more
still listening and answering /). Stopped those two by their exact,
individually-verified PIDs -- confirmed via `ps` immediately before,
never a pattern-matched kill -- since AgentsView's own tooling had no
way to reach them.

Verified end to end against the real installed v0.42.0 binary:
check_disabled_agents(prefix) -> () with zero new agentsview processes
started, confirmed by the same before/after process-count method used
throughout this investigation.

Signed-off-by: yihou <yihou@amd.com>
wiring measured that HOME=$AGENT_SYS_HOME (already present in the env
dict at all three subprocess.run call sites) is a second, stronger
gate 3 that nobody designed: AgentsView computes every provider's
*default* session root from HOME, so pointing it at the prefix means
every root it could scan resolves inside the prefix -- no denylist
needed, and unlike OTHER_PROVIDERS this gate cannot go stale when
upstream adds a provider we have never heard of. Measured with doctor
sync run under exactly this environment: 122 roots listed, 0 outside
the prefix.

Design doc (da7f5e0) already documents this as gate 5, in words that
say the gate exists by accident rather than by contract because no
test held it -- a one-line "tidy up the env dict" edit would have
silently restored every default root to the user's real home with
nothing else here going red.

Added the assertion to the existing spy-based test for ensure_running,
plus two new tests for check_disabled_agents and discover_providers
(the other two call sites). Confirmed each actually catches the
regression: mechanically stripped HOME from all three env dicts,
watched all three tests go red with the exact KeyError this assertion
exists to prevent, restored, confirmed green again -- the same
discipline used earlier for _patched_environ and the completeness
check.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
…d the orphan trap

Three measurements from PHASE0 that a future maintainer would otherwise
have to rediscover:

- AGENTSVIEW_NO_DAEMON=1 was tried and rejected. It makes health/projects/
  session list refuse outright rather than fall back to a direct SQLite
  read, even against a populated database -- which in this function's own
  error handling collapses to a permanently empty result: a check that is
  a no-op forever and never looks broken.
- serve stop / daemon stop only reach the most recently started daemon;
  an orphan is unreachable by any documented command.
- A validation path must never start a daemon: health does, doctor sync
  does not, and delegating the port choice is the rule this component
  exists to hold.

Signed-off-by: yihou <yihou@amd.com>
Signed-off-by: yihou <yihou@amd.com>
@dorado269
dorado269 changed the base branch from main to dev.yaoc.aiopt.task_package September 4, 2026 04:00
@dorado269
dorado269 marked this pull request as draft September 4, 2026 04:00
@dorado269
dorado269 changed the base branch from dev.yaoc.aiopt.task_package to dev.yihou.aiopt.task_package September 4, 2026 04:02
Comment thread .claude/CLAUDE.md
@@ -1,107 +1,107 @@
# Task — `e2e_deploy_standardized`: the first stage of the LLM e2e optimisation task package
# Task — wire **AgentsView** into `agent_sys` as its o11y panel

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not modify this file when submit to git

via `assignment.environment`; this subprocess is not one and so was never
covered. It inherited the ambient environment and dropped one JSONL into
`~/.claude/projects` on every single run — measured during acceptance by
matching the prompt string above to the file. Small, and still a breach of

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments is too looooong, keep every not file level comment in at most 5 lines, better 3 line


outcome = outs[-1]
if outcome.level == "ok":
# **Validated here, at install time, not left for `serve` to discover

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answer: brief me what this file does

…d no line

`set -e` is exempt for a non-final command in an `&&` list, so
`[ -n "$expected" ] && [ "$expected" = "$actual" ]` short-circuited and
carried on to install an unverified binary whenever the SHA256SUMS line was
missing -- the one case that test was written for. A real mismatch aborted
correctly the whole time, which is why it read as working.

Split into two statements, and ${tmp:?} in the cleanup trap so a recursive
delete on an empty variable aborts rather than expands.

Three tests run the recipe's install string for real against a stubbed curl:
checksum matching, mismatched, and not found. The first is the positive
control -- without it the other two pass against a recipe that installs
nothing at all.

Signed-off-by: yihou <yihou@amd.com>
…a test

Both the URL and the first-install notice were log.info. Nothing in this
repository configures logging, so the root logger sits at WARNING with no
handler and both lines were discarded -- while the o11y failure paths, being
log.warning, reached stderr through logging.lastResort. The component
reported only its failures. The tests passed because caplog.at_level("INFO")
forces the level from pytest's side, which is a test asserting intent rather
than output.

Both now go through the event stream, which is the thing in this package
whose job is being read. New EventKind O11Y_PANEL, SCHEMA_VERSION 1.3 -> 1.4.
Verified on a real invocation: the line appears on stdout and as
{"kind": "o11y_panel", "url": ...} in --json.

Also the wire nobody tested: deleting the _start_o11y call from main()
outright left the whole tests/cli suite green, so acceptance criterion 2
-- deploying agent_sys starts it -- had no test. Five added, driving main()
for real. And the installer drift guard invented its own skip message when
the branch produced none and then asserted only that it was truthy; it now
drives the real skip branch via a check_cmd that succeeds.

Signed-off-by: yihou <yihou@amd.com>
… file

_owns_port read a file of ours holding just a port number, written only by a
successful launch and never expired. That is evidence about the past: after
our daemon died and the user started their own AgentsView on the same port,
both gates passed and the operator was handed a URL to a panel listing every
session on their machine, labelled as theirs, with no warning -- the single
requirement this component exists to satisfy, failing silently.

The witness is now AgentsView's own daemon.<pid>.json, read out of our own
AGENTSVIEW_DATA_DIR. A stranger's daemon writes its record into their data
directory, so it structurally cannot appear here; the isolation is the
filesystem's rather than a convention we maintain. Measured on a real
v0.42.0: the record is removed by AgentsView on a clean `serve stop`, so the
ordinary case leaves no stale evidence, and the unclean case (SIGKILL, OOM,
reboot) is caught by checking the recorded pid for liveness. Read only;
AgentsView stays unmodified. Verified against the live resident daemon.

This also retires the orphan trap. The port file was written only after the
health check passed, so one slow cold start left a live daemon nobody could
recognise and the panel was skipped on every subsequent run, permanently,
blaming a stranger. AgentsView writes its record when it starts. The two
busy-port cases are now reported separately, because their fixes are
opposites, and the last unguarded write in the module is gone with the file.

Alongside:
- resolve_port range-checks; bind answers an out-of-range port with
  OverflowError, which is not OSError and escaped to the CLI's blanket
  backstop. 0 is rejected too: it binds, then delegates the port choice to
  AgentsView's auto-discovery, which this module exists to prevent.
- _identifies_as_agentsview catches http.client.HTTPException. Measured: a
  truncated Content-Length body does *not* reach it (read(amt) returns short),
  but chunked framing does -- and chunked is what a Go server sends with no
  length.
- write_config writes by rename. A partial read of that file is not a crash,
  it is disabled_agents coming back short, i.e. every provider re-enabled.
- one _binary_env(prefix), where HOME=$AGENT_SYS_HOME was spelled out at three
  call sites with a test holding each; the named timeout the module was
  missing; and the Agent-roots parse bounded at the next unindented line.

Signed-off-by: yihou <yihou@amd.com>
… no $HOME

It did environ["HOME"] and raised KeyError with neither AGENT_SYS_HOME nor
$HOME set -- a systemd unit, a stripped cron environment, `env -i`.
material.py caught it and warned; prepare.py and cli/environment.py did not.
One condition, three behaviours, two of them fatal, on a feature the run
never asked for. The line prepare.py replaced could not fail at all.

resolve now falls back to $HOME, then the passwd entry, then a per-uid
directory in $TMPDIR, and expanduser/resolve the override so `~/foo` and a
relative path both mean what they look like -- the cwd moves between zones.

test_a_failed_link_does_not_raise reached its warning by deleting $HOME,
which was the bug rather than the fixture; it now makes the prefix
unreachable through the filesystem, where a real failure would come from.

Signed-off-by: yihou <yihou@amd.com>
…ility rules

Three things the review changed and the design did not yet say: reuse is
gated on AgentsView's own daemon.<pid>.json rather than a port file of ours
(with the measurement that it is removed on a clean stop, and why the old
witness failed in two directions at once); success goes to the event stream
because log.info in this package reaches nobody; and resolving the prefix is
not one of the operations allowed to fail.

Signed-off-by: yihou <yihou@amd.com>
…dy knew

A run tree was browsable only if you already knew the uuids: `handoffs/<uuid>/`,
`zones/task.<uuid>.<attempt>.<hash>/`. The closure name and the handoff kind
were both in hand at the moment the directory was created, and both thrown
away. They are now the second field:

    handoffs/handoff.solutions_a.57882e8c-.../v0/content/
    zones/task.main.2c0260e5-.../task.solve_a.310209e3-....0.ce4e41e0/
    zones/task.main.2c0260e5-.../validation.solve_a.310209e3-....output_validation.01a143f0/

Two properties keep the label from becoming a contract:

- **Nothing resolves through it.** `find_zone_dir` matches the uuid as a whole
  field, and `handoff_dir` finds the directory whose name *is* the uuid or ends
  with `.<uuid>`. So a store or zone tree written before labels existed resolves
  unchanged, and a renamed closure does not move an artefact.
- **`.` does not survive a label.** The uuid is the last field before the
  attempt/version, and `parts[-2]` would read the wrong one otherwise.

The staged copy in a zone takes the store directory's own basename rather than
recomputing the label, which keeps `task_graph`'s kind vocabulary out of
`stage`, `stage_handoffs` and `prepare_validation`.

`handoff.store.version_dir` and `env_mgr.fs.layout.handoff_version_dir` stay the
two sanctioned writers of the shape; `tests/interfaces/test_handoff_layout.py`
pins both, including that either alone would regress the compatibility rule.
The five body-side `examples/*/assets/lib/store.py` readers get the same
scan-then-fallback rule under the same agreement test.

Verified: 2266 passed / 3 skipped / 4 xfailed; `examples/demo2` run end to end
and the tree above read off it; that run's store renamed back to bare uuids and
re-read through all three readers.

Signed-off-by: yihou <yihou@amd.com>
feat(agent_sys): label every runtime directory with the name it alrea…
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