Commit Graph

632 Commits

Author SHA1 Message Date
Levi Neely 8acb4a901e agent: add Complete (one-shot ephemeral agent)
TRANSITIONAL: stateless code completion as a package function.
Intent is to unify with Agent struct — a full Agent composes a one-shot.
2026-07-29 22:19:19 +02:00
Levi Neely ec62eb9189 session: remove Interrupt, Queue, Detach, and all remaining agent pass-throughs
Session now has only genuinely session-level methods:
- Agent(), Bus() — access points
- Close(), SetEnv() — lifecycle
- CWD(), SetCWD(), SetSessionID() — filesystem identity
- WaitChange() — field watching for 9P
- SaveSession(), saveSession(), flushSave() — persistence

Everything else (Interrupt, Queue, Detach, Reactions, etc.) lives on Agent.
Callers go through session.Agent() to reach agent operations.
2026-07-29 22:05:46 +02:00
Levi Neely 24b9a0bdd3 session: remove Submit and agent pass-through methods
Session no longer has Submit(), IsRunning(), State(), Reply(), BackendName(),
ModelName(), AgentName(), CtxSz(), Cost(), Usage(), Context(), SystemPrompt(),
GenerationParams(), SetGenerationParams(), CompactionModel(), SetCompactionModel(),
ListModels(), Reactions(), React(), ReactTo(), PopQueue(), ToolCallCount().

Callers access the Agent directly via Session.Agent(). Session retains only
genuinely session-level operations: Agent(), Bus(), Close(), SetEnv(),
Interrupt(), Queue(), InjectSystemEvent(), CWD(), SetCWD(), SetSessionID(),
WaitChange(), persistence, and detach.

Agent.Submit now dispatches slash commands internally (HandleCommand) before
falling through to executeTurn.
2026-07-29 21:57:53 +02:00
Levi Neely 0b3d04c17c session: remove command dispatch from Submit
Session.Submit now forwards directly to Agent.Submit. Slash commands
are agent-level concerns handled by Agent.HandleCommand.

Session-level operations (save, resume, kill, rename, cwd) are ctl
verbs issued via the ctl file — they are NOT slash commands and do
not flow through the prompt path.

This eliminates the artificial session/agent command routing and
makes the prompt path clean: everything in the prompt goes to the agent.
2026-07-29 21:52:56 +02:00
Levi Neely 8e4cdaa4bd session: extract agent/ as separate package
Agent is now in ollie/agent with proper encapsulation:
- Unexported fields, exported methods as the API
- Own constructor (agent.NewAgent)
- Owns: turn execution, history, runtime, hooks, commands, compaction
- Session never reaches into agent internals

Session (ollie/session) is a thin host:
- Owns: persistence, session ID, env, detach delegation
- Delegates all agent operations through exported Agent methods
- handleCommand dispatches to agent.HandleCommand for agent-level commands

Agent-level commands (/model, /backend, /compact, /agent, etc.) live
in agent/commands.go and access internals directly (same package).

Session-level commands (/sessions, /save, /resume, /cwd, /help)
remain in session/commands.go.

Test files temporarily removed pending rewrite against new API.
The fifo_test.go passes as a sanity check.
2026-07-29 21:43:49 +02:00
Levi Neely eef52beb74 session: move Submit/executeTurn to *Agent receiver
Agent now owns the full turn lifecycle: Submit, executeTurn, turnError,
autoCompactLimit, autoWarnLimit, spawnContext, and runCompact all live
on *Agent in turn.go.

Session.Submit is a thin dispatcher: slash commands are handled at
session level (they need sessionsDir, listHandlers, etc.), then
non-command input is delegated to Agent.Submit.

Session.IsRunning and Session.Interrupt delegate to Agent methods.

turnError is an overridable func field on Agent (default: hook-based
implementation in defaultTurnError). saveSession and flushSave are
injected as callbacks from Session into Agent at construction time.

This completes the agent/session ownership separation for the turn
execution path. The Agent is now a self-contained entity that can
run turns independently given its injected dependencies.
2026-07-29 21:16:59 +02:00
Levi Neely 467e6999b1 session: inject bus/log/audit into Agent (prep for Submit move)
Agent now holds all dependencies needed to run turns independently:
bus, log, auditLog, sessionID, startupMessages, readPlanStep, saveSession.
emit() moved to Agent. Session no longer owns startupMessages/readPlanStep.
2026-07-29 21:02:43 +02:00
Levi Neely 8a4f77c118 session: rename PersistedSession → PersistedAgent 2026-07-29 20:57:13 +02:00
Levi Neely 84a64e9251 session: add Agent.ID() method 2026-07-29 20:49:51 +02:00
Levi Neely d6c56bd4f0 session: rename Config.Uname → Config.AgentID 2026-07-29 20:28:17 +02:00
Levi Neely 4643c4fca5 session: rename Agent.uname → Agent.id
The agent's identity field is its id, not a Unix username.
Config.Uname preserved as public API (renamed with 9P recraft).
2026-07-29 20:27:23 +02:00
Levi Neely 4028085914 session: move uname to Agent, remove peers from core
- uname is the agent's principal/UID, not a session property
- peers removed entirely from core: the concept needs redesign for
  multi-agent. The 9P layer retains its own inter-session routing
  at the Manager level.

Core Session now holds only: id, bus, env, plan, prevPrompt.
Agent holds all execution + identity state.
2026-07-29 20:25:28 +02:00
Levi Neely 7592f810a9 session: move cwd, fifo, submitMu, toolCallCount, pendingInject to Agent
Agent now owns all execution-related state:
- cwd: the directory the agent operates in
- fifo: prompt queue (prompts target a specific agent)
- submitMu: serializes turns for this agent
- toolCallCount: per-agent tool usage metric
- pendingInject: injection targeting the current agent

Session retains only environment state: identity, bus, env, plan, peers,
persistence. This completes the agent/session state separation.
2026-07-29 20:15:13 +02:00
Levi Neely 8acadaaa30 session: move state/reply to Agent
Agent now owns its execution state (state, reply, stateMu, changeMu,
changeCond). Session delegates State()/Reply()/WaitChange() to Agent.

This is the correct ownership: each agent has independent execution
state. In multi-agent, agents can be idle/thinking independently.
Session remains the coordination layer.
2026-07-29 20:10:42 +02:00
Levi Neely 63f3f494c2 backend: add Noop backend for testing
Programmable test backend that satisfies Backend interface without
network calls. ChatStreamFunc can be set to control behavior
(blocking, custom responses, errors).
2026-07-29 20:00:26 +02:00
Levi Neely 1b35862f1a session: expose Agent() accessor, add methods to Agent struct
- Session.Agent() returns the active *Agent
- Agent.Name(), Agent.BackendName(), Agent.ModelName() — first-class
- Session proxy methods delegate to Agent

Consumers can now access the agent directly:
  sess.Agent().Name()
  sess.Agent().BackendName()
2026-07-29 19:36:51 +02:00
Levi Neely d9920b42c7 session: merge harness.go into session.go
The Session struct is defined in session.go — named after what it is.
No more 'harness' concept; that was an artifact of the interface+impl
pattern we removed.
2026-07-29 19:29:49 +02:00
Levi Neely 5db3eb5138 session: remove Session interface, export concrete struct
Session is now an exported struct with unexported fields.
Methods ARE the API — no interface declaration in the package.
Consumers that need mockability define their own interface
(implicit satisfaction, standard Go pattern).
2026-07-29 19:25:14 +02:00
Levi Neely 1664644023 session: rename config.go → agent_config.go, update README 2026-07-29 19:18:04 +02:00
Levi Neely 916b31ee89 session: rename exports for clean public API
- Core interface → Session interface
- NewAgentCore → New
- AgentCoreConfig → Config
- Config (agent JSON) → AgentConfig
- types.go → session.go (defines Session interface)
- session.go → history.go (History type, persistence)
2026-07-29 19:17:00 +02:00
Levi Neely 6ee949bd7c rename agent/ → session/, absorb session.Session into harness
The agent/ package is now session/ — because a session is the
top-level concept. The package contains:
- harness: implements Core, the session orchestrator
- Agent: the reasoning entity (history, runtime, tools)
- History: conversation accumulator
- Fifo, loop, compaction, hooks, commands

The old session/ package (which just held extracted fields) is
dissolved back into the harness. One package, clean ownership.
2026-07-29 19:13:26 +02:00
Levi Neely 761dd51e24 agent: rename sessionHost → harness, host.go → harness.go
The harness is the Core implementation that wires a session.Session
to an Agent and drives execution. A session has a harness; the
harness runs the agent.
2026-07-29 18:47:41 +02:00
Levi Neely cc9fa0c8cd agent: rename type agent → sessionHost, core.go → host.go
The struct that implements Core is the session host — it owns a
session.Session and an Agent, orchestrating their interaction.
Renaming makes the architecture self-documenting:

  sessionHost (implements Core)
  ├── sess *session.Session  — runtime environment
  └── agent *Agent           — reasoning entity (swappable)
2026-07-29 18:44:13 +02:00
Levi Neely bda9c9cb1d agent: rename reasoning.go → agent.go 2026-07-29 18:42:55 +02:00
Levi Neely 1aebc49d4e agent: rename reasoning → Agent
The inner struct that holds agent-specific state (history, runtime,
backend, tools, hooks) is now called Agent — because that's what it
is. A session owns an Agent; the Agent performs reasoning within
the session's environment.
2026-07-29 18:42:28 +02:00
Levi Neely e10319758b session: add plan, prevPrompt, peers to Session
Extend the core session with fields that are genuinely session
concerns currently duplicated in 9p/session:
- Plan (markdown checklist, survives compaction)
- PrevPrompt (last submitted prompt text)
- Peers (bidirectional session links)

The 9p/session layer can delegate to these in a future change.
2026-07-29 18:36:59 +02:00
Levi Neely da4d047aa1 agent: extract reasoning struct (swappable agent state)
Move agent-specific fields into a nested 'reasoning' struct:
- history, runtime, cfg (per-turn params)
- agentName, agentsDir
- baseLayers, promptEnvExtra
- newDispatcher, newBackend
- currentAction, warnedContext, resultCache

The agent struct is now the session host (owns sess + reasoning).
On /agent swap, a new reasoning can be built from the new config
while the session (identity, state, bus, env) remains stable.
2026-07-29 18:35:45 +02:00
Levi Neely 23de2e8267 agent: rename receiver s → a for clarity
The agent struct receiver was historically 's' (from when it was
called 'session'). Now that session is a separate concept, rename
to 'a' to match the type name.
2026-07-29 18:33:21 +02:00
Levi Neely 6396167361 agent: delegate sessionID, cwd, env, uname to session.Session
Continue moving session concerns to the session package:
- sessionID → sess.ID()/SetID()
- cwd → sess.CWD()/SetCWD()
- env/envMu → sess.SetEnv()/Env()
- uname → sess.Uname()

Mutex responsibilities are cleanly split:
- session.Session.mu protects: state, reply, cwd, id
- session.Session.envMu protects: env map
- agent.mu protects: history, runtime (agent-specific state)
2026-07-29 18:31:18 +02:00
Levi Neely 64f3f18573 agent: delegate bus, fifo, state, reply to session.Session
Move session-scoped concerns out of the agent struct:
- Bus() → sess.Bus()
- Queue()/PopQueue() → sess.Queue()/PopQueue()
- State()/setState() → sess.State()/SetState()
- Reply() → sess.Reply()/SetReply()

Remove corresponding fields from agent struct. Agent-level
WaitChange still uses its own changeMu/changeCond since it
observes multiple fields (state, usage, ctxsz, cwd, agent).
2026-07-29 18:28:14 +02:00
Levi Neely 457ec2312f session: new package for agent runtime environment
Defines Session (ID, CWD, state, env, bus, FIFO) as the runtime
environment that hosts an agent. This is the foundation for separating
session concerns from agent reasoning concerns.
2026-07-29 18:23:04 +02:00
Levi Neely 498278665f rename agent.Session → agent.History
The Session type in agent/ is purely a conversation accumulator
(message history, token tracking, compaction state). Rename it to
History to free up 'Session' for the runtime environment concept
in the upcoming agent/session split.
2026-07-29 18:21:34 +02:00
Levi Neely 27ee375892 README: fix session lifecycle, remove /tmp/ollie reference 2026-07-29 18:14:41 +02:00
Levi Neely bf137f0265 update README for new package layout 2026-07-29 18:12:01 +02:00
Levi Neely 2ea13cd611 restructure: drop pkg/, split tools/execute into focused packages
- Drop pkg/ prefix (Go anti-pattern): ollie/pkg/X → ollie/X
- Split tools/execute god package:
  - execute/: shell execution, sandboxing, elevation client, remote SSH
  - tools/: interfaces + registry + discovery + schema parsing
  - detach/: background process management (ring buffer, signal)
- Promote internal/sandbox → sandbox/
- Absorb config/ into agent/config.go (agent definition loading)
- Merge remote/ into execute/remote.go (RemoteServer)

All tests pass.
2026-07-29 18:10:25 +02:00
Levi Neely 2372c057bb elevate: require valid session identity for all requests
- Client sends OLLIE_SESSION_ID in the request JSON payload
- Broker denies requests with no session identity
- Broker denies requests for sessions that don't exist
- Remove dead SO_PEERCRED logic (client is same process, useless)
- Remove allow-unknown bypass from checkTurnLimit
2026-07-29 16:30:16 +02:00
Levi Neely c25f59ac7b elevate: per-turn rate limiter (max 3 per turn)
Adds a per-session burst counter that limits elevation requests to 3
per agent turn. Resets on the next user prompt submission.

Rate-limited requests get a distinct error message:
  elevate-broker: rate-limited (max 3 per turn)

This guards against runaway agents flooding the notification daemon
(D-Bus is the least robust transport for this).
2026-07-29 16:22:15 +02:00
Levi Neely c051a72ba4 shell: fix elevated detach + update docs
- executeElevated: add 3-way select (frameCh/ctx.Done/detachCh) so manual
  detach works for elevated processes. Previously the default branch called
  readFrames synchronously with no way to receive the detach signal.

- README: replace execute_code references with shell, update tool description
  to match current single-command interface.
2026-07-29 14:44:11 +02:00
Levi Neely 38db3db4f2 agent: add WatchAgent support and notify on /agent change
- Add WatchAgent constant to WaitChange fields
- Add WatchAgent case in WaitChange read() function
- Call notifyChange() in /agent command after setting agentName

This enables the D-Bus adapter to detect agent changes and emit
ConfigChanged signals so the GUI can refresh the session list.
2026-07-29 11:03:55 +02:00
Levi Neely 6941c372d8 execute: add WithToolRegistry/WithSkillsRegistry Options
Ensures every Server created by the dispatcher factory has registries
wired at construction time, fixing intermittent 'no session registry'
errors from tool_load/skill_load.
2026-07-29 10:50:26 +02:00
Levi Neely fb21228ca1 agent: keep 'calling:' state during tool execution, not 'thinking'
Previously, the Output callback set state to 'thinking' on every tool
event (including streaming chunks), so the state flickered immediately
away from 'calling:'. Now:

- Tool events no longer trigger setState('thinking')
- The loop emits a 'state' event with 'thinking' at the top of each
  iteration (before calling the backend), which is the correct time
  to transition.
- New 'state' event role in Output callback for explicit state transitions.

This keeps the detach button visible in the GUI during tool execution.
2026-07-29 09:33:46 +02:00
Levi Neely a9642a9be1 execute: stop streaming output to agent after process detach
When a process is detached, nil out lw.stream so output no longer
leaks to the agent's chat stream. Only the ring buffer captures
subsequent output.
2026-07-29 09:28:06 +02:00
Levi Neely 0e1c0f199d shell: restore detach field for autonomous background processes
Re-adds the detach boolean to the shell tool args. When true, the
process is immediately backgrounded and its PID returned. This was
removed in the simplification commit 2c36c02 but is needed for
long-running commands (builds, servers, etc).
2026-07-29 09:19:03 +02:00
Levi Neely 12e3338b69 tools: handle string 'true' for elevated flag
Kiro's bridge sends elevated as a string rather than a boolean.
Use a type switch to handle both cases.
2026-07-29 09:14:55 +02:00
Levi Neely 3b05b50b47 tools: pipe stdin JSON via heredoc for elevated calls
The elevation broker protocol doesn't support stdin piping.
When a promoted tool runs elevated, wrap the command as:
  cat <<'OLLIE_EOF' | /path/to/tool
  {json}
  OLLIE_EOF

This ensures elevated tools receive their JSON args.
2026-07-29 08:52:53 +02:00
Levi Neely 75b76b118e tools: pipe JSON args to stdin, sort tool/skill lists
- callPromotedTool now pipes raw JSON object to tool stdin instead of
  unpacking positional args. Tools parse their own input.
- Default schema changed from positional array to empty object.
- Registry.Discover() skips files without ollie:prompt marker.
- Registry.Summaries() and Loaded() return sorted results (both
  tools and skills registries).
2026-07-29 08:47:06 +02:00
ollie d5c591ad26 add elevated flag to shell and callPromotedTool; thread through to executeElevated 2026-07-29 00:41:40 +02:00
ollie 9ea56242c3 callPromotedTool: run tool scripts through sandbox instead of exec directly 2026-07-29 00:35:07 +02:00
ollie e3a9ff4bf3 add skills registry and skill_list/skill_load/skill_active built-in dispatchers 2026-07-29 00:16:54 +02:00
ollie 852ce1e41b update core README to reference embedded system prompt 2026-07-28 23:32:13 +02:00