Skip to content

Releases: AgamiAI/agami-core

0.6.6 — the activity log can name the execution it ran

Choose a tag to compare

@vishalkalbi27 vishalkalbi27 released this 17 Aug 06:39
5e10bd5

Two changes since 0.6.5: one that closes a gap the schema documented but never filled, and one fix.

The activity log can now name the execution it ran. tool_calls records that a tool ran; query_executions records what the warehouse was asked and what the guard decided. Both are written for every execute_sql, they are 1:1, and nothing joined them — migration 018's own comment recorded the gap and chose to duplicate a column rather than add a key.

That reasoning held while the only thing wanting a join was a single sentence-shaped field. It stops holding once anything has to attach to a statement: correlation_id names the turn, one turn runs several statements, and no column told them apart. A reader wanting per-statement facts had to guess, and the only material to guess with was the agent's own framing text — which repeats whenever a turn retries a sub-question, so it mis-attributes in silence.

tool_calls now carries audit_id. The value is one the caller already received: _emit stamps it onto every envelope and query_executions.id is that same id, so the column reconciles nothing and mints nothing. It is read off the body record_tool_call already parses — on ok, refused and failed alike, because a blocked or broken call is the one an auditor most wants to trace — and a caller that hands over no body can state it instead, like the outcome fields beside it. NULL is the ordinary case, not a gap: a schema read or a datasource list runs no statement and has none.

A client asking what this server looks like gets the icon, not a 401. /favicon.ico was not merely absent, it was gated: the bearer middleware challenges anything off its public list before routing can 404 it, so the one unauthenticated request a client makes to learn what a server looks like came back as an auth prompt. An MCP client showing a server in a connector list has no credentials to offer for an icon fetch, and a deployment that redirects / elsewhere leaves this the only path left to ask on — so such a server drew a letter avatar beside servers that answered.

No migration needed on the consumer side. The new column is nullable and added by ALTER, the parameter defaults to None, and the derive only captures a value that was previously parsed and discarded — so no existing caller changes behaviour.

PR #230 · PR #231

0.6.5 — a model that joins one table pair twice now loads

Choose a tag to compare

@ashwin-agami ashwin-agami released this 13 Aug 05:20
2ef87d7

One fix since 0.6.4, and it is a blocker rather than an improvement.

A semantic model whose subject area joins the same pair of tables in two different ways could not be loaded into the database at all. Each relationship row was keyed by its two table names, and that key is the table's primary key — so the second join collided with the first and the write raised part-way through. The failure is total rather than partial: nothing commits, so the deployment ends up serving no semantic model whatsoever, and the error names a column tuple rather than the join that collided.

Self-referencing tables make this ordinary rather than exotic — an employee row pointing at another as its manager, and again as its mentor, is one pair of tables and two genuinely different joins. Two on:-expression joins between one pair, and same-named tables living in two schemas, failed the same way.

The key now carries the relationship's position within its subject area, so a collision is impossible for any model that validates, rather than impossible for the shapes somebody enumerated — which is what the two previous versions of this key each attempted, and each missed.

No migration. Nothing reads the value (a relationship is rebuilt from its stored document), and a redeploy replaces a datasource's rows wholesale, so models written under the old key need no backfill.

PR #228 · issue #227

0.6.4 — the claim page says whose account it is

Choose a tag to compare

@ashwin-agami ashwin-agami released this 12 Aug 05:21
042cdab

Added

  • The claim page says whose account it is. Somebody following a setup or reset link was asked to
    choose a password with no indication of which account it belonged to. A link is shared out of band,
    so the person holding it may have been sent the wrong one, or two of them — and now that a link can
    REPLACE a working password rather than only set a first one, following the wrong one silently locks
    somebody out of their own account with nothing on the page to warn them. It discloses nothing new:
    whoever holds the link already holds a token naming that account.
  • The MCP surface states what the server already does. Four capabilities shipped working and
    undocumented, and a silent feature is worse than a broken one — a broken one gets reported.
    execute_sql returns three statuses and described two; the third carries the whole database-error
    channel with a kind from a declared enum, which tells an agent whether a failure is repairable
    from the schema it already holds or whether retrying is pointless. Documented, so it can be used.

Fixed

  • A password refused for being too long said "use at least 8 characters" — the same message as
    one refused for being too short, and advice that makes the problem worse the more carefully it is
    followed. Each bound now names itself.
  • Seven store-step tests resolved the profile from the developer's own ~/agami-artifacts rather
    than from the temporary directory they had set, so they compared against a value from a file the
    test never mentions. CI has no such file, so they passed there and failed only on a machine that
    had run the CLI.

v0.6.3 — an administrator can give a colleague a new password

Choose a tag to compare

@ashwin-agami ashwin-agami released this 11 Aug 05:25
f71f3c2

Added

  • An administrator can give a colleague a new password. The setup link works exactly once —
    claiming an account flips it out of pending, and every later use is refused — so an administrator
    could create somebody's account and then never help them back into it, and with no mail path there
    is no self-service reset either. /claim now carries a second purpose: setup still means a
    pending account choosing its first password, and reset means a claimed account being given a new
    one because an administrator asked. The two are matched against the state of the account rather
    than trusted from the link, so neither can do the other's job.
  • A reset link is single-use, by a mechanism the setup link does not need: a reset leaves an
    account exactly as claimed as it was, so nothing about it would change and the link would work
    forever. The link is bound to the credential it was minted against, and that credential is a
    condition of the write — so the first successful reset retires it, a password change by any other
    route retires it, and two simultaneous uses cannot both succeed.
  • A reset ends the sessions running on the old password by revoking that person's refresh
    tokens. Stated precisely because a security control believed to cover more than it does is worse
    than a narrow one: a session already holding an access token lasts until that token expires, and
    the /admin console cookie and any outstanding authorization code are untouched.
  • A reset can never give a password to an identity that signs in through a provider, and can never
    revive a switched-off account.

Changed

  • The claim page is worded for what the link is for — somebody with an account is not told to
    "finish setting up" — and setup_page_html / setup_done_html are now claim_page_html /
    claim_done_html, which take the purpose.
  • Password hashing on /claim runs off the event loop. It is deliberately slow and memory-hard, and
    this is a public endpoint, so hashing inline stalled every other request in flight.
  • get_datasource_schema answers within the scope the caller declares, and the never-hide
    guarantee is now stated relative to that scope: within it, nothing is hidden — the scope's own
    metrics plus the cross-area bucket. A new area parameter narrows to one subject area
    (completing the set execute_sql and get_prompt_examples already had); dataset_names
    narrows to those tables. query and metric_names rank and select detail and do not scope,
    because narrowing on something the caller never declared is silent deprivation. The response
    echoes the scope it resolved.
  • The appended domain context no longer repeats the subject-area listing that the same response
    already carries as structured subject_areas.

Fixed

  • A table-scoped get_datasource_schema call returned columns and no way to join them. It had
    resolved the relationships and metrics for those tables and discarded them, while the tool's own
    description promised both. It now returns them, with each metric carrying the binding for the
    deployment's engine — so one call carries the columns, the joins and the declared expression.
  • The instructions named a metric field the payload does not send. Both instruction surfaces
    told the agent to reuse a metric's calculation/bindings VERBATIM; the payload key is
    binding, singular, already resolved to this deployment's engine. An agent told in capitals to
    reuse a field it never receives hand-rolls the SQL instead — and hand-rolled SQL does not reduce
    to the declared binding, so the receipt reads unmatched on a column that does compute the
    metric. The text and the payload are now pinned to each other by a test derived from the
    projection itself.

v0.6.2 — an omitted datasource names the choices, and the tool surface tells the truth

Choose a tag to compare

@ashwin-agami ashwin-agami released this 09 Aug 15:02
5e64b3f

Three PRs since 0.6.1.

Fixed

An omitted datasource names the org's real datasources instead of one nobody has (#218).
Resolution used to fall through to the literal 'default' on a deployment serving several datasources, and the caller heard no such datasource: default. Two audiences read that and both were misled — an administrator sees a name no customer has and concludes their data has gone missing; a model sees a failed lookup and invents another name, because the tool's own description told it a default existed.

Both sides are fixed. The three tool descriptions now say what is true on each install and point at list_datasources, and an omitted argument gets the catalogue back (datasource_required) while a named one that does not exist is still told so. An empty string counts as omitted, matching resolve_profile's own if explicit:.

A CASE predicate is not what a SUM aggregates, and a shared expression names no metric (#215).

Changed

The tool surface reports what it checked, and accepts what it advertises (#216) — nine findings from a connector audit, all one shape: the server stated something it had not established, or named a field the agent could not reach. resolve_profile consults the store; a hosted server no longer claims "All execution is local"; bindings is actually sent; area reaches get_prompt_examples; list_datasources returns description.

A note on the version

This is a patch even though #216 removes model_present and for_questions_about from emitted tool payloads — normally a minor bump under 0.x.

It ships as a patch because every removed field was provably dead: model_present could only ever be the literal True on that path (the listing is built from the model rows), and for_questions_about had no writer anywhere in the product. A consumer reading either was reading a constant or an empty list.

for_questions_about stays declared on the model format — those models forbid unknown keys and generation wrote it to disk — so no model on disk fails to load.

Full changelog: https://github.com/AgamiAI/agami-core/blob/main/CHANGELOG.md

v0.6.1

Choose a tag to compare

@ashwin-agami ashwin-agami released this 08 Aug 17:14
3ee1880

A patch release: one fix, no interface changes.

Full notes: CHANGELOG.md

Fixed

A refusal's own sentence reaches the audit row again. tool_calls.refusal_detail and tool_calls.refusal_remediation — the two columns 0.6.0 added so a row says what the decision was made against and what you were told — were NULL on every refusal a real deployment recorded. The rule was recorded; neither sentence was.

The coherence rule that shipped beside them cleared both whenever a caller stated the outcome instead of leaving it to be parsed from the response body. That is right for a caller who contradicts the body and wrong for every real one: the served MCP transport states the outcome for every tool that speaks the Envelope, and a consumer's own sink states what it observed. So the sentences survived on exactly one path — a caller that hands over a body and states nothing — and nothing in production takes it.

They are now kept when the stated outcome agrees with the body and dropped when it does not. What the rule guarded is unchanged: a stated success has no refusal to explain, and a stated kind naming a different failure is describing something these sentences are not about.

Upgrading

Nothing to do. No argument, return shape, refusal rule, receipt key or CLI flag moves — a column that was empty starts carrying the sentence it was always supposed to. If you are coming from 0.5.x, read the v0.6.0 notes first; that release carries the breaking changes.

v0.6.0

Choose a tag to compare

@ashwin-agami ashwin-agami released this 08 Aug 06:36
b77f0c5

The trust receipt becomes five sections on every answer, the guard reads your SQL in your own engine's grammar, and a self-hosted server that cannot record a query no longer runs it.

Full notes: CHANGELOG.md

Upgrading

Updating the plugin now upgrades the agami-core library with it. Before this release the launcher only checked whether the library imported — which a stale one does — so a plugin update could leave new skills running against an old library. Since 0.6.0 changes the receipt shape, that combination would have failed every charted query on receipt.columns is missing. The launcher now compares the installed distribution against the plugin version and reinstalls with --upgrade when it is behind.

If you pin agami-core yourself, read the breaking changes below before upgrading.

Breaking

  • max_rows is no longer an argument to execute_sql, and --max-rows is gone from the CLI. Ask for the rows you want in the statement.
  • A result over AGAMI_SQL_MAX_ROWS is refused, not trimmed. truncated is gone from a successful result.
  • Agami no longer rewrites your SQL to fix a fan-out join — your statement runs byte for byte.
  • Four correctness checks (fan trap, chasm trap, SUM of a rate, SUM of a balance across time) report instead of refusing; what they found rides on the receipt under aggregates.
  • sensitive is a description, not a gate — marking a column sensitive no longer blocks projecting it. If a value must not come back, exclude the column from the model or deny it to the connecting role.
  • The model_safety refusal rule is gone; every refusal now names the gate that chose it.
  • Receipt: the old flat keys are replaced by five sections (tables_usedtables.items[], relationshipsjoins.items[], metricscolumns.items[] with a non-null metric, warnings → derived from joins.items[]). scope gained a set-operation arm ordinal (main#1), which breaks an equality branch on main / cte:x.
  • sm prepare returns {sql, findings, units} and always exits 0; sm preflight returns {findings: [...]}; sm receipt --applied-filters is gone.
  • Adapters.governance is removed. Adapters is not keyword-only, so a positional 4th argument now binds as executor — construct it by keyword.

Operators, read this

AGAMI_GOVERNANCE_ENFORCED ships off by default. A fresh server does not enforce table scope, column scope, the SELECT * ban, or the engine-mismatch check until you turn it on. The read-only guard, the dangerous-function guard, the statement timeout and the row cap enforce in both postures, as does a read-only database role. Nothing ever claims the checks ran — every answer and audit row says when they are off, and the server logs a warning at startup. See self-hosting and SECURITY.md.

Also: a datasource that does not declare storage_connections[].storage_type now refuses queries — an engine we cannot name is one we cannot parse for.

v0.5.3

Choose a tag to compare

@ashwin-agami ashwin-agami released this 31 Jul 06:53
f8ae324

Added

  • Override seam on the tool-call audit log (record_tool_call). An embedder that dispatches the
    tool handlers itself can now supply the values the log would otherwise infer from the model's own
    tool arguments and result body — source, thread_id, correlation_id, user_question,
    org_id, and the outcome (success, row_count, error_kind). Every override defaults to
    None = "derive it the way you always have", so a caller passing none of them gets byte-identical
    rows; the in-repo transport path is regression-pinned identical to before the seam existed.
    _record_query's source is likewise readable from a context var, so the query log and the
    tool-call log cannot disagree about what drove one execution.

Changed

  • The audit row now fails toward honesty. A caller with no result body to parse can record a
    failure (previously the row defaulted to success — the one direction an audit log must never fail
    in); a raise outranks every override; naming an error_kind is itself a statement of failure; and
    a success never carries an error kind, so a row cannot contradict itself.
  • The Activity drawer's · self-reported marker now reflects real provenance. source is
    projected through the read path and a per-turn flag drives the marker, which is dropped only where
    every call in a turn agrees on a single non-default source. Unset sources, mixed sources and empty
    turns all keep it — the marker signals lower trust, so overstating trust is the direction that
    must never happen. · agent-reported on agent_query is unchanged on every path.

Full detail in CHANGELOG.md · PR #169

v0.5.2

Choose a tag to compare

@ashwin-agami ashwin-agami released this 31 Jul 05:00
446cc20

Added

  • Per-request tool-visibility seam (Adapters.tool_visibility). A consumer can now narrow the advertised MCP tool surface per caller via an optional tool_visibility(tool_name) -> bool on the adapters, applied in build_server. It is applied at both the list and the call seam (filtering only the list would leave an unlisted tool callable by name); a hidden tool answers as absent (the same Unknown tool a typo gets), not refused, so the list can't be used as an oracle for what's withheld; a predicate that raises hides the tool rather than granting it, and is logged rather than propagated. Subtractive only — descriptions and input schemas pass through untouched. None (the OSS default) is byte-identical to prior behaviour. See CHANGELOG.md for full detail.

Full Changelog: v0.5.1...v0.5.2

v0.5.1

Choose a tag to compare

@ashwin-agami ashwin-agami released this 30 Jul 05:15
16060cb

Security

  • Closed a read-only-guard bypass via a welded quoted identifier. A double-quoted identifier is self-delimiting on both ends, so the guard's lexer could fuse neighbouring tokens (FROM"pg_class"FROMpg_class), destroying the word-boundary anchor every deny-list pattern relies on — the gate stopped seeing the token and returned no rejection. The lexer now re-supplies a separator on either side only when the quote actually separated two word characters. See CHANGELOG.md for full detail.

Full Changelog: v0.5.0...v0.5.1